C ++ uitbreidingsfuncties?

Zijn er uitbreidingen voor C ++ zoals er zijn in C #?

Bijvoorbeeld in C# kunt u doen:

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

someuint.SwapEndian();

Is er zoiets in C ++?

13
Nee, C ++ heeft niets zoals dit. AFAIK, er zijn ook geen voorstellen voor C ++ 17 (schrijf er een!). De D-programmeertaal heeft UFCS (syntaxisfunctie voor uniforme functieoproep), wat precies "uitbreidingsmethoden" is. Het zou een leuke functie zijn om te hebben, aangezien je niet-leden niet-vriendenfuncties kunt schrijven en de objectparameter kunt vermijden, resulterend in code die van links naar rechts gelezen kan worden in plaats van "van-naar-uit/rechts-naar- links".
toegevoegd de auteur gnzlbg, de bron
Dit is een van de vele redenen waarom C ++ niet meer als een geldige programmeertaal kan worden beschouwd. Het is te oud, heeft niet veel functies van moderne talen zoals C#, en is het tegenovergestelde van productief. Microsoft moet opnieuw beginnen met een nieuwe gecompileerde taal die lijkt op C #/Java. Als iemand me vertelt dat ze C ++ gebruiken, lach ik naar ze en zie ik ze meteen als minder intelligent dan ik.
toegevoegd de auteur Krythic, de bron

7 antwoord

Er zijn geen uitbreidingsfuncties in C ++. Je kunt ze gewoon definiëren als vrije functies.

uint SwapEndian(uint value){ ... }
5
toegevoegd
Dit is het enige antwoord dat de originele code gebruikt; dank je. Het toont ook een voorbeeld van wat wordt bedoeld met vrije functies en de meeste andere reacties hier en elders doen dat niet. Sommige antwoorden in andere threads die hiermee verband houden, geven een lange filosofische beschrijving van OOP en hoe het zou moeten werken, maar missen nuttige details.
toegevoegd de auteur user34660, de bron

Uitbreidingsmethoden (en ook "statische klassen") bestaan ​​alleen in C #/Java-talen omdat de ontwerpers hebben besloten dat (de Java-manier van) OOP de One True Way is en dat alles een methode uit een klasse moet zijn:

Dit is geen C ++ manier om dingen te doen. In C ++ hebt u naamruimten, vrije functies en Koenig zoeken om het gedrag van een klasse uit te breiden:

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

Ik zie uitbreidingsmethoden meestal als schadelijk. Als je te veel gedrag aan een klasse koppelt, sla je er waarschijnlijk niet in de reden vast te leggen waarom de klasse bestaat. Ook (zoals "deelklassen") hebben ze de neiging om de code gerelateerd te maken aan een niet-lokale klasse. Dat is slecht.

Wat uw probleem betreft, doet u in C ++ eenvoudigweg:

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;
}

Gebruik:

swap_endian(42);

of, als het type kan worden afgeleid:

std::uint64_t x = 42;
std::uint64_t y = swap_endian(x);
5
toegevoegd
@gnzlbg, overeengekomen. Ze waren soms best handig in C#, maar C# -stijl uitbreidingsmethoden namen altijd dit aan als niet-const T & (referentie). In het geval dat b (a) equivalent is aan ab() , moet het eerste argument van b een aanwijzer of verwijzing zijn ? Zou het ooit logisch zijn om te kopiëren? Ik zou graag zien dat deze syntaxis beschikbaar is in C ++ en vraag me af of er belemmeringen zijn voor de introductie, behalve desinteresse van de gemeenschap en de commissie.
toegevoegd de auteur Drew Noakes, de bron
Uitbreidingsmethoden zijn gewoon syntactische suiker .. je kunt een vrije functie act_on_bar gebruiken en gebruiken als act_on_bar (b) of b.act_on_bar() . De tweede manier heeft niets te maken met de echte OO-weg , alleen met leesbaarheid. Bijv. als je 2 functies hebt; act2 (act1 (b)) is minder leesbaar dan b.act1 (). act2() . In C ++ moet u act1 en act2 lidfuncties maken om leesbare code te krijgen, zelfs als het logischer zou zijn om ze als vrije functies te hebben ! Het zou ook het generieke programmeren verbeteren. En leesbaar is hier niet subjectief, aangezien de meeste mensen tekst van links naar rechts lezen, geen in-om-uit.
toegevoegd de auteur gnzlbg, de bron
Maar het principe kan zeker zonder lessen worden toegepast. Voor onbewerkte C om functies aan klassen te binden zonder ze in een steeds groter wordende structuur te forceren die een klasse is. Ik wil C waar ik functies aan structuren of primitieven kan binden zonder enige koppeling of ongecontroleerde verminking (aangezien ik mezelf bind, is er geen verminking, omdat het tijdens de compilatie wordt vertaald). Vrije abstracties met nul abstractie lekken in resulterende binaries is prachtig.
toegevoegd de auteur Dmitry, de bron

Niet zo, maar je kan operator overloads schrijven die werken op klassen die je niet hebt geschreven, en het lijkt een beetje op soortgelijke methode-extensies (maar niet voor benoemde functies, alleen voor operators die niet zijn geweest al door die klasse gedefinieerd). Het klassieke voorbeeld is om je klas te laten werken met 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

Dit is natuurlijk een triviaal voorbeeld, maar je begrijpt het wel.

3
toegevoegd

Niet op een direct-analoge manier, maar vele malen kun je het gewenste effect bereiken met behulp van sjablonen. U kunt geen methoden "toevoegen" aan een concrete klasse in C ++ zonder af te leiden van de oorspronkelijke klasse, maar u kunt functiesjablonen maken die met elk type werken.

Hier is bijvoorbeeld een functiesjabloonbibliotheek die ik gebruik om conversies van het ntoh-type van elk integraal type uit te voeren:

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
toegevoegd
@Alexandre: het is hetzelfde als het uwe. Het enige verschil is dat ik specialisaties heb verstrekt voor ingebouwde typen die kunnen worden afgehandeld door ntohs en ntohl .
toegevoegd de auteur John Dibling, de bron
Dit is een vrij complexe manier om de taak uit te voeren.
toegevoegd de auteur Alexandre C., de bron
in ntohx hoe weet u dat de twee 32-bits waarden in de geretourneerde array in de juiste volgorde staan? ;)
toegevoegd de auteur Géza Török, de bron

Nee, sorry, maar er is niets zoals dat in C ++ en het kan ook nooit zo zijn. Er zijn veel dingen die de standaard als implementatieafhankelijk laat (de compiler kan het op elke gewenste manier doen), en ook C ++ heeft geen gestandaardiseerde ABI .

1
toegevoegd
@Sean: bij extensiemethoden moet dit scenario worden ondersteund: u neemt de headers van sommige externe bibliotheken A en uw eigen code (bibliotheek B) die uitbreidingsmethoden definiëren en gebruiken voor de typen die zijn gedefinieerd in A. Uw linker zou dan werk uit hoe je de gesprekken kunt aansluiten op een mogelijk vooraf gebouwd binair bestand van A dat heel goed geproduceerd kan zijn door een andere compiler/linker . Tenzij er standaardisatie is op een ABI (wat eerlijk gezegd niet zal gebeuren als gevolg van BC-onderbrekingen) kun je dat nooit doen.
toegevoegd de auteur Jon, de bron
@Sean: Anders gezegd: in .NET moeten assembly's uiterst gedetailleerde metadata bevatten over de typen die ze bevatten. In C ++ OTOH is er absoluut geen garantie dat er iets in een binaire tot zelfs hint zit dat het een klasse bevat die je hebt gedefinieerd, laat staan ​​de naam en andere details.
toegevoegd de auteur Jon, de bron
Ik denk om te zeggen "het kan ook nooit" is een beetje onredelijk. Ze zouden een of andere vorm van uitbreidingsmethodemechanisme kunnen toevoegen als ze dat wilden, ze hebben er juist voor gekozen om het niet te doen.
toegevoegd de auteur Sean, de bron

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.

Uiteindelijk wordt het een kwestie van stijl, onderhoudbaarheid, betrouwbaarheid en zuiverheid van de code die u probeert te produceren. Men zou stellen dat het definiëren van de "SubtractValue" -methode met twee argumenten efficiëntere code creëert, maar anderen zouden beweren dat de overbelaste methode beter te onderhouden is. Uiteindelijk wordt het aan de architecten en ontwikkelaars overgelaten om te bepalen wat belangrijk is voor hun project. Ik geef alleen maar een mogelijke oplossing voor het probleem.

#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;
}

De bovenstaande code-uitvoer is "Waarde is 20".

1
toegevoegd

Als u verwijst naar de parameter this -qualified, dan nee. Maar er zijn misschien nog andere slimme trucs, afhankelijk van uw specifieke use-case ... Kunt u meer details geven?

1
toegevoegd