Функції розширення C ++?

Чи існують розширення для C ++, як у C #?

Наприклад, в C# ви можете зробити:

public static uint SwapEndian(this uint value)
{
    var tmp = BitConverter.GetBytes(value);
    Array.Reverse(tmp);
    return BitConverter.ToUInt32(tmp, 0);
}

someuint.SwapEndian();

Чи є щось подібне до C ++?

13
Ні, C ++ не має нічого подібного. AFAIK, жодних пропозицій щодо C ++ 17 немає (пишіть один!). Мова програмування D має UFCS (уніфікований синтаксис виклику функції), що точно є "методами розширення". Було б непоганою функцією, адже ви могли б писати функції, що не є членами, та не використовувати параметр об'єкта, в результаті чого код, який можна читати зліва направо, замість "in-to-out/right-to- ліворуч ".
додано Автор gnzlbg, джерело
Це одна з багатьох причин, чому C ++ більше не можна вважати дієвою мовою програмування. Це забагато, не має численних особливостей сучасних мов, таких як C#, і є протилежним продуктивності. Microsoft треба починати знов за допомогою нової скомпільованої мови, яка виглядає як C #/Java. Якщо хто-небудь говорить мені, що вони використовують C ++, я смію над ними і відразу бачу їх менш інтелектуальними, ніж я.
додано Автор Krythic, джерело

7 Відповіді

У C ++ немає функцій розширення. Ви можете просто визначити їх як вільних функцій.

uint SwapEndian(uint value){ ... }
5
додано
Це єдиний відгук, який використовує вихідний код; спасибі. Він також показує приклад того, що мається на увазі за допомогою вільних функцій , і більшість інших відповідей тут і в інших місцях немає. Деякі відповіді в інших темах, пов'язаних з цим, дають довгий філософський опис ООП і як він повинен працювати, але бракує корисних відомостей.
додано Автор user34660, джерело

Методи розширення (а також "статичні класи") існують у мовах C #/Java виключно тому, що дизайнери вирішили, що (по-англійському) OOP є "єдиним правильним шляхом", і що все має бути методом класу:

Це не C ++ спосіб робити речі. У C ++ у вас є простір імен, безкоштовні функції та Пошук Koenig , щоб розширити поведінку класу:

namespace foo
{
    struct bar { ... };

    void act_on_bar(const bar& b) { ... };
}

...

foo::bar b;
act_on_bar(b);//No need to qualify because of Koenig lookup

Я зазвичай вважають методи розширення шкідливими. Якщо ви додаєте до класу надто велику поведінку, ви, напевно, не в змозі відобразити причину існування класу. Також (наприклад, "часткові класи"), вони, як правило, роблять код, пов'язаний з класом, не локальним. Що погано.

Що стосується вашої проблеми, то в C ++ ви просто:

template 
T swap_endian(T x)
{
    union { T value; char bytes[sizeof(T)]; } u;
    u.value = x;

    for (size_t i = 0; i < sizeof(T)/2; i++) 
        swap(u.bytes[i], u.bytes[sizeof(T) - i - 1]);

    return u.value;
}

Використання:

swap_endian(42);

або, якщо тип може бути виведено:

std::uint64_t x = 42;
std::uint64_t y = swap_endian(x);
5
додано
@gnzlbg, домовилися. Вони були досить зручними в C# час від часу, однак методи розширення стилю C# завжди приймали цей як неконстентний T & (reference). Якщо b (a) еквівалентно ab() , чи повинен перший аргумент b бути вказівником або посиланням ? Чи було б коректно копіювати? Я хотів би, щоб цей синтаксис був доступний на C + + і цікавився, чи існують будь-які перешкоди для його впровадження, окрім відсутності інтересу до спільноти та комітету.
додано Автор Drew Noakes, джерело
Методи розширення - це просто синтаксичний цукор. Ви можете мати безкоштовну функцію act_on_bar і використовувати її як act_on_bar (b) або b.act_on_bar() . Другий спосіб має нічого для роботи з справжнім методом OO , тільки з читаемостью. Наприклад, якщо у вас 2 функції; act2 (act1 (b)) менш читабельно, ніж b.act1 (). act2() . У C ++ вам потрібно зробити функції-члени act1 і act2 , щоб отримати читаний код, навіть якщо було б більше сенсу, щоб вони були вільними функціями ! Це також покращить загальне програмне забезпечення. І читабельність тут не суб'єктивна, тому що більшість людей читають текст зліва направо, немає вставки.
додано Автор gnzlbg, джерело
Але, безумовно, принцип можна застосувати без класів взагалі. Для сировини C, щоб зв'язувати функції до класів, не змушуючи їх перетворюватися в постійно зростаючу структуру, яка є класом. Я хочу, щоб C, де я можу зв'язувати функції до структур або примітивів без будь-яких зв'язків і неконтрольованого перекручування (тому що я зобов'язуюсь, немає ніяких скриптів, тому що він перекладається на час компіляції). Безкоштовні абстракції з нульовою абстракцією витікають у результуючі двійкові файли красиві.
додано Автор Dmitry, джерело

Це не так, але ви можете записувати перевантаження операторами, які працюють на класах, які ви не писали, і це трохи нагадує розширення методу (але не для названих функцій, тільки для операторів, які ще не були вже визначений цим класом). Класичним прикладом є робота класу з cout :

class MyClass {
public:
    MyClass(const char* blah) : str(blah) { }

    const char* string() const {
        return str;
    }

private:
    const char* str;
};

// this is kinda like a method extension
ostream& operator<<(ostream& lhs, const MyClass& rhs) {
    lhs << rhs.string();
}

// then you can use it like this
MyClass m("hey ho");
cout << m;

// prints hey ho

Звичайно, це тривіальний приклад, але ви отримуєте ідею.

3
додано

Не в прямий аналогічний спосіб, але багато разів ви можете досягти бажаного ефекту за допомогою шаблонів. Ви не можете "додавати" методи до конкретного класу в C ++, не виводячи їх з початкового класу, але ви можете створювати шаблони функцій, які працюють з будь-яким типом.

Наприклад, ось бібліотека шаблонів функцій, яку я використовую для перетворення типу ntoh будь-якого інтегрального типу:

template inline Val ntohx(const Val& in)
{
    char out[sizeof(in)] = {0};
    for( size_t i = 0; i < sizeof(Val); ++i )
        out[i] = ((char*)&in)[sizeof(Val)-i-1];
    return *(reinterpret_cast(out));
}

template<> inline unsigned char ntohx(const unsigned char & v )
{
    return v;
}
template<> inline uint16_t ntohx(const uint16_t & v)
{
    return ntohs(v);
}

template<> inline uint32_t ntohx(const uint32_t & v)
{
    return ntohl(v);
}

template<> inline uint64_t ntohx(const uint64_t & v)
{
    uint32_t ret [] =
    {
        ntohl(((const uint32_t*)&v)[1]),
        ntohl(((const uint32_t*)&v)[0])
    };
    return *((uint64_t*)&ret[0]);
}
template<> inline float ntohx(const float& v)
{
    uint32_t const* cast = reinterpret_cast(&v);
    uint32_t ret = ntohx(*cast);
    return *(reinterpret_cast(&ret));
};
2
додано
@Alexandre: це так само, як і ваша. Єдина відмінність полягає в тому, що я надавав спеціалізації для вбудованих типів, які можуть обробляти ntohs і ntohl .
додано Автор John Dibling, джерело
Це досить складний спосіб виконати поставлене завдання.
додано Автор Alexandre C., джерело
в ntohx як ви знаєте, два 32-бітних значень в зворотному масиві в правильному порядку? ;)
додано Автор Géza Török, джерело

Ні, вибачте, але в C ++ це нічого подібного, і це ніколи не може бути. Є багато речей, які стандарт видає як залежність від реалізації (тобто компілятор може це робити будь-яким способом воно віддає перевагу), а також у C ++ немає стандартизованого ABI .

1
додано
@Sean: для підтримки цього сценарію потрібні способи розширення: ви берете заголовки деякої сторонньої бібліотеки A і свій власний код (бібліотека B), який визначає та використовує методи розширення для типів, визначених в A. Тоді ваш лінкер повинен мати Розкажіть, як підключити дзвінки до можливому попередньо встановленому двійковому файлу , який дуже добре може бути зроблено іншим компілятором/лінером . Якщо не існує стандартизація ABI (що, чесно кажучи, не відбудеться через перерви БК), ви ніколи не зможете це зробити.
додано Автор Jon, джерело
@ Сеан: Поставте інший спосіб: у .NET агрегати повинні містити надзвичайно докладні метадані щодо типів, які вони містять. У C ++ OTOH немає абсолютно ніякої гарантії, що в бінарному файлі є щось, що навіть містить натяк на те, що він містить визначений вами клас, не забувайте про його назву та інші деталі.
додано Автор Jon, джерело
Я думаю, що сказати "це також ніколи не може бути" є трохи необгрунтованим. Вони могли б додати певну форму механізму розширення, якщо вони хотіли, вони просто не обрали цього.
додано Автор Sean, джерело

One method I have found is to use the overloaded ">>" operator with lambda expressions. The following code demonstrates this. You have to know to use operator ">>" instead of "->", this is because the compiler I use will not allow the operator "->" to be overloaded. Also because the operator ">>" has lower precedence than the "->" you have to use parentheses to force to compiler to evaluate the equation in the correct order.

Зрештою це стає предметом стилю, ремонтопридатності, надійності та чистоти коду, який ви намагаєтеся створити. Можна стверджувати, що визначення методу "SubtractValue" з двома аргументами створює більш ефективний код, але інші стверджують, що перевантажений метод є більш ретельним. Зрештою архітектори та розробники залишать за мету з'ясувати, що важливо для свого проекту. Я просто пропоную можливе вирішення проблеми.

#include 
#include 
#include 
#include 

// Some plain demo class that cannot be changed.
class DemoClass
{
public:
    int GetValue() { return _value; }
    int SetValue(int ivalue) { _value = ivalue; return _value; }
    DemoClass *AddValue(int iadd) { this->_value += iadd; return this; }

private:
    int _value = 0;
};

// Define Lambda expression type that takes and returns a reference to the object.
typedef std::function DemoClassExtension;

// Overload the ">>" operator because we cannot overload "->" to execute the extension.
DemoClass* operator>>(DemoClass *pobj, DemoClassExtension &method)
{
    return method(pobj);
}

// Typical extensions.

// Subtract value "isub".
DemoClassExtension SubtractValue(int isub)
{
    return [=](DemoClass *pobj) {
        pobj->AddValue(-isub);
        return pobj;
    };
}

// Multiply value "imult".
DemoClassExtension MultiplyValue(int imult)
{
    return [=](DemoClass *pobj) {
        pobj->SetValue(pobj->GetValue() * imult);
        return pobj;
    };
}

int _tmain(int argc, _TCHAR* argv[])
{
    DemoClass *pDemoObject = new DemoClass();
    int value = (pDemoObject->AddValue(14) >> SubtractValue(4) >> MultiplyValue(2))->GetValue();
    std::cout << "Value is " << value;
    return 0;
}

Вищенаведений код - "Значення 20".

1
додано

Якщо ви маєте на увазі це -qualified параметр методу, то немає. Але можуть бути й інші розумні трюки в залежності від вашого конкретного випадку використання ... Чи можете ви надати більш детальну інформацію?

1
додано
IT KPI C/С++ новым годом
IT KPI C/С++ новым годом
747 учасників

Чат обсуждения С/С++. - Вопросы "напишите за меня лабу" - это оффтоп. - Оффтоп, флуд, оскорбления и вбросы здесь не приняты. - За нарушение - предупреждение или mute на неделю. - За спам и рекламу - ban. Все чаты IT KPI: https://t.me/itkpi/1147