'class' : assignment operator could not be generated



  • Ich benutze, wie von CStoll hier im Forum http://www.c-plusplus.net/forum/viewtopic-var-t-is-189009-and-highlight-is-ersatz+finally.html empfohlen, eine Hilfsklasse für eine Critical Section. Quelltextauszug:

    // foo.h 
    class CAcqCriticalSection
    {
      private:
        CRITICAL_SECTION FSection;
      public:
        CAcqCriticalSection(void); 
        virtual ~CAcqCriticalSection(void);
        void Enter(void);
        void Leave(void);
    };
    
    class CCSLock
    {
      CAcqCriticalSection& m_sect;
    public:
      CCSLock(CAcqCriticalSection& sect)
      : m_sect(sect)
      { m_sect.Enter(); }
      ~CCSLock()
      { m_sect.Leave(); }
    };
    
    class CFoo {
     ...
      public:
        CAcqCriticalSection cs;
     ...
    }
    //foo.cpp
    CAcqCriticalSection::CAcqCriticalSection(void)
    {
      InitializeCriticalSection(&FSection);
    }
    
    CAcqCriticalSection::~CAcqCriticalSection(void)
    {
      DeleteCriticalSection(&FSection);
    }
    
    void CAcqCriticalSection::Enter(void)
    {
      EnterCriticalSection(&FSection);
    }
    
    void CAcqCriticalSection::Leave(void)
    {
      LeaveCriticalSection(&FSection);
    }
    
    // Benutzung:
    void CFoo::foo(void)
    {
      // cs is Membervariable der Klasse CFoo 
      CCSLock Enter(cs);
      ...
    }
    

    Alles scheint OK. Doch heute habe ich ein Unit-Test-Projekt erzeugt, das den Quelltext einbindet. Beim Kompilieren bekomme ich nun einen Warnhinweis (VC++ 2005; Habe die Zeilennummern an obigen Quelltextausschnitt angepasst):

    foo.h(22) : warning C4512: 'CCSLock' : assignment operator could not be generated
            foo.h(14) : see declaration of 'CCSLock'
    

    Ich frage mich nun:
    1. Vermute ich richtig, dass "assignment operator could not be generated" bedeutet, das kein Kopierkonstruktor vom Compiler angelegt werden kann?
    2. Warum kommt die Warnung nicht schon beim Hauptprojekt? Nur dort benutze ich CCSLock. Eine Zuweisung von CCSLock benutze ich definitiv nicht. Denke auch, dass der Compiler dann eine Fehlermeldung statt einer Warnung generieren würde.
    3. Wie könnte der Kopierkonstruktor implementiert werden? Quelle und Ziel müssten beide die selbe Critical Section benutzen. Bei meinen zwei Versuchen

    // 1. Versuch
    class CCSLock
    {
      CAcqCriticalSection& m_sect;
    public:
      CCSLock(CAcqCriticalSection& sect)
      : m_sect(sect) { }
      CCSLock(const CCSLock& src)
      : m_sect(src.m_sect)
      { }
      ~CCSLock()
      { m_sect.Leave(); }
    };
    
    // 2. Versuch
    class CCSLock
    {
      CAcqCriticalSection& m_sect;
    public:
      CCSLock(CAcqCriticalSection& sect)
      : m_sect(sect)
      { m_sect.Enter(); }
      CCSLock(const CCSLock& src)
      : m_sect(src.m_sect)
      {m_sect = src.m_sect;}
      ~CCSLock()
      { m_sect.Leave(); }
    };
    

    blieb die Warnung einfach bestehen.



  • Nein, der Kopier-Konstruktor bereitet keien Probleme, sondern der Zuweisungsoperator (die Klasse CSLock enthält eine Referenz und deswegen funktioniert der compilergenerierte op= nicht). Und ich würde diese Klasse komplett unkopierbar machen, indem Copy-Ctor und operator= privat deklarier (und nicht definiert) werden.



  • CStoll schrieb:

    ... ich würde diese Klasse komplett unkopierbar machen, indem Copy-Ctor und operator= privat deklarier (und nicht definiert) werden.

    Ich weiß nicht was ein Copy-Ctor ist und wie man ein Copy-Ctor bzw. eine Operation abstrakt deklariert weiß ich auch nicht :(. Ein Beispiel oder ein Link diesbezüglich würde mir sehr helfen.

    edit:
    Ok habe selbst herausbekommen das Ctor die Abkürzung von Constructor ist.



  • Typische Vertreter:

    class A {
       A(); // Default-Ctor
       A(A const&); // Copy-Ctor
       A const& operator=(A const&); // Assignment-operator
    };
    

    mittels anderen const/volatile-Kennzeichnern oder Defaultparametern kann man die Signaturen auch verändern, aber prinzipiell ist ein Copy-Ctor, ein Konstruktor, mit dem man ein Objekt aus einem anderen derselben Klasse erzeugen("initialisieren") kann.

    Gruß,

    Simon2.



  • BerndD schrieb:

    CStoll schrieb:

    ... ich würde diese Klasse komplett unkopierbar machen, indem Copy-Ctor und operator= privat deklarier (und nicht definiert) werden.

    Ich weiß nicht was ein Copy-Ctor ist und wie man ein Copy-Ctor bzw. eine Operation abstrakt deklariert weiß ich auch nicht :(.

    "Copy-Ctor" ist eine normalerweise übliche Abkürzung für "Kopier-Konstruktor". Und ich sagte nicht "abstakt", sondern "privat" 😉

    Ein Beispiel oder ein Link diesbezüglich würde mir sehr helfen.

    No Problem:

    class no_copy
    {
      int& my_ref;
    public:
      no_copy(int& tgt)
      : my_ref(tgt)
      {}
    
    private:
      //copy-Ctor privat deklariert
      //Achtung: keinen Funktionsrumpf angeben
      no-copy(const no_copy&);
      //das selbe für op=
      no_copy& operator=(const no_copy&);
    };
    

    Dadurch, daß die Kopier-Methoden definiert wurden, legt der Compiler keine automatisch erzeugte Version an. Und weil sie privat sind, kann niemand Objekte dieser Klasse kopieren oder zuweisen (die compilergenerierte Version ist automatisch public).
    (und in den seltenen Fällen, daß Klassenmethoden oder friend's die Klasse kopieren wollen, wird sich der Linker querstellen, weil die Methoden keinen Rumpf haben)



  • Danke für eure Antworten!

    CStoll schrieb:

    BerndD schrieb:

    CStoll schrieb:

    ... ich würde diese Klasse komplett unkopierbar machen, indem Copy-Ctor und operator= privat deklarier (und nicht definiert) werden.

    Ich weiß nicht was ein Copy-Ctor ist und wie man ein Copy-Ctor bzw. eine Operation abstrakt deklariert weiß ich auch nicht :(.

    "Copy-Ctor" ist eine normalerweise übliche Abkürzung für "Kopier-Konstruktor". Und ich sagte nicht "abstakt", sondern "privat" 😉
    ...

    Mit "abstrakt" meine ich eine deklarierte Memberfunktion, die keine Definition hat. Bin Delphi Programmierer und habe den Begriff "Abstrakt" hier falsch verwendet weil mir kein besserer eingefallen ist.

    Mir war bisher nicht bekannt, dass man in C++ die Definition einfach weglassen kann! Eigentlich müsste der Kompiler das als Fehler melden!? Macht er offensichtlich nur, wenn versucht wird die nicht definierte Memberfunktion zu benutzen.

    Ich verstehe immer noch nicht woher plötzlich die Warnung herkam. Aber wahrscheinlich ist das eine Eigenart des Kompilers und ich sollte mir nicht weiter den Kopf drüber zerbrechen.

    Hier mein Umsetzung anhand eurer Hilfestellung:

    class CCSLock
    {
    private:
      CAcqCriticalSection& m_sect;
      CCSLock const& operator=(CCSLock const&);
      CCSLock(const CCSLock& src);
    public:
      CCSLock(CAcqCriticalSection& sect)
      : m_sect(sect)
      { m_sect.Enter(); }
      ~CCSLock()
      { m_sect.Leave(); }
    };
    

    P.S.: Was ist der Unterschied zwischen ein Kopierkonstruktor und einen Zuweisungskonstruktor? Eigentlich doch nur ein Syntaktischer bei der Verwendung? Das was sie tun ist doch das selbe oder?



  • Den Compiler stört es überhaupt nicht, wenn eine Funktion nicht definiert ist - in C++ gibt es idR mehrere Übersetzungseinheiten und wenn die Definition nicht gefunden wird, liegt sie vermutlich in einer anderen ÜE. Erst der Linker (der alle ÜE's zu einem Programm zusammenführt) stellt fest, ob alle verwendeten Funktionen auch tatsächlich definiert sind.



  • @CStoll: Ich verstehe, ist ja auch ganz praktisch. Hatte zu sehr die "Delphi Sichtweise" im Kopf.



  • BerndD schrieb:

    Ich verstehe immer noch nicht woher plötzlich die Warnung herkam. Aber wahrscheinlich ist das eine Eigenart des Kompilers und ich sollte mir nicht weiter den Kopf drüber zerbrechen.

    Nun, der Compiler versucht von selbst, dir den op= zu generieren, da du ihn nicht angegeben hast. Das tut er offenbar unabhaengig davon, ob du ihn benutzt oder nicht. Da er die Standardgenerierung des op= nicht hinbekommt, kriegst du die Warnung, nur fuer den Fall dass du dich drauf verlassen hast dass es diesen automatischen op= gibt. Haettest du den op= auch benutzt, haette der Linker dir gesagt, "undefined reference to operator=" - und ohne die Warnung haettest du dich dann eventuell gefragt "aber der sollte doch eigentlich...?!?" - Daher die Warnung 😉

    P.S.: Was ist der Unterschied zwischen ein Kopierkonstruktor und einen Zuweisungskonstruktor? Eigentlich doch nur ein Syntaktischer bei der Verwendung? Das was sie tun ist doch das selbe oder?

    Es gibt nur den Kopierkonstruktor und einen Zuweisungsoperator, aber keinen Zuweisungskonstruktor.
    Der Copy-Ctor erstellt ein neues Objekt anhand eines anderen, der Zuweisungsoperator veraendert ein bereits vorhandenes Objekt anhand eines anderen. Was man als "Zuweisungskonstruktor" bezeichnen koennte ist folgendes:

    Foo x = y;
    

    Was aber meines Wissens nur eine andere Schreibweise fuer den Copy-Ctor ist (evetl mit einer zusaetzlichen temporaeren Kopie, bin mir da nicht ganz sicher - Die Gurus moegen mich berichtigen ;)).



  • pumuckl schrieb:

    BerndD schrieb:

    Ich verstehe immer noch nicht woher plötzlich die Warnung herkam. Aber wahrscheinlich ist das eine Eigenart des Kompilers und ich sollte mir nicht weiter den Kopf drüber zerbrechen.

    Nun, der Compiler versucht von selbst, dir den op= zu generieren, da du ihn nicht angegeben hast. ...

    Ja, das ist klar. Ich wundere mich nur warum diese Warnung nicht gleich kam. Ich habe die Deklaration, die Definition und die Anwendung der Klasse. Keine Warnung! Binde ich das ganze aber in ein Testprojekt ein - dass diese Klasse gar nicht direkt benutzt! - dann kommt plötzlich die Warnung. Dafür gibt es keine vernünftige Erklärung und das nervt mich fast überhaupt nicht.

    pumuckl schrieb:

    P.S.: Was ist der Unterschied zwischen ein Kopierkonstruktor und einen Zuweisungskonstruktor? Eigentlich doch nur ein Syntaktischer bei der Verwendung? Das was sie tun ist doch das selbe oder?

    Es gibt nur den Kopierkonstruktor und einen Zuweisungsoperator, aber keinen Zuweisungskonstruktor.
    Der Copy-Ctor erstellt ein neues Objekt anhand eines anderen, der Zuweisungsoperator veraendert ein bereits vorhandenes Objekt anhand eines anderen. Was man als "Zuweisungskonstruktor" bezeichnen koennte ist folgendes:

    Foo x = y;
    

    Was aber meines Wissens nur eine andere Schreibweise fuer den Copy-Ctor ist (evetl mit einer zusaetzlichen temporaeren Kopie, bin mir da nicht ganz sicher - Die Gurus moegen mich berichtigen ;)).

    Ich denke hier liegst du genauso falsch wie ich es war. Genau das Bespiel

    Foo x = y;
    

    meinte ich, als ich dachte beides wäre gleich, d.h.

    Foo x = y;
    // entspricht
    Foo x(y);
    

    Aber nein, im 1. Fall wird das Objekt x erzeugt und dann der Zuweisungsoperator darauf angewendet. Der Copy-Ctor kommt nur im 2. Fall zum Einsatz. Im Default Fall dürfte das Ergebnis gleich sein. Es liegt aber in der Freiheit des Programmieres den Copy Ctor und/oder den Zuweisungsoperator zu überschreiben und damit beliebiges Verhalten zu bewirken.

    Das mit den Operator Überschreiben ist eine nette Idee für Theoretiker. Die glauben dann auch noch die Programme würden dadurch lesbarer werden 🙄.



  • @BerndD: die warning von MSVC kannst du vergessen. Ist ja nur ne Warning 😉 (und keine von den "Feuer am Dach" Warnings, sondern eine von den "halt doch die Schnauze dummer VC" Warnings).

    Allerdings sollten beide Klassen (CAcqCriticalSection und CCSLock) jeweils nicht kopierbar und nicht zuweisbar sein.
    In beiden Fällen kommt IMO Blödsinn raus, sowohl beim Kopieren als auch beim Zuweisen.

    Ergo solltest du den copy-ctor sowie den assignment operator private machen, oder von sowas wie boost::noncopyable ableiten (was einfach auch bewirkt dass kein copy-ctor und kein assignment operator vom compiler erzeugt werden können, was in dem Fall erwünscht und gut und richtig ist).



  • BerndD schrieb:

    Ich denke hier liegst du genauso falsch wie ich es war. Genau das Bespiel

    Foo x = y;
    

    meinte ich, als ich dachte beides wäre gleich, d.h.

    Foo x = y;
    // entspricht
    Foo x(y);
    

    Aber nein, im 1. Fall wird das Objekt x erzeugt und dann der Zuweisungsoperator darauf angewendet.

    Hast du das getestet oder geraten?

    (nach meinem Kenntnisstand sind beide Schreibweisen identisch (fast - ersteres scheitert, wenn der notwendige Konstruktor explizit ist), aber du kannst mich gerne vom Gegenteil überzeugen)



  • BerndD schrieb:

    ...
    Ich denke hier liegst du genauso falsch wie ich es war. Genau das Bespiel

    Foo x = y;
    

    meinte ich, als ich dachte beides wäre gleich, d.h.

    Foo x = y;
    // entspricht
    Foo x(y);
    

    Aber nein, im 1. Fall wird das Objekt x erzeugt und dann der Zuweisungsoperator darauf angewendet. Der Copy-Ctor kommt nur im 2. Fall zum Einsatz...

    Also dann würdest du meine jahrelangen C++ Erfahrungen gänzlich durcheinander bringen...

    ...
    Foo x;      // Foo::Foo(); Konstruktor
    Foo y = x;  // Foo::Foo(const Foo&); Kopierkonstruktor
    Foo z(x);   // Foo::Foo(const Foo&); Kopierkonstruktor
    z = y;      // Foo& Foo::operator=(const Foo&); Zuweisungsoperator
    ...
    

    Wenn es auf einen Compiler anders sein sollte ist dies IMHO ein schwerwiegender Fehler des Compilers.

    cu André



  • CStoll schrieb:

    Hast du das getestet oder geraten?

    Ich hatte anhand gelesenes geraten. Gebe zu, dass ich den Zeitaufwand fürs Testen mir sparen wollte. Hier nun der Test

    #include <stdio.h>
    #include <string.h>
    
    char ID = 'A';
    
    class CFoo
    {
    public:
      char s[1024];
      CFoo& operator=(const CFoo& src);
      CFoo(const CFoo& src);
      CFoo(void);
      ~CFoo(void);
    };
    
    CFoo& CFoo::operator=(const CFoo& src)
    {
      sprintf_s(s,1024,"%s wurde %s zugewiesen", s, src.s);
      printf("In Zuweisungsoperator: %s\r\n", s);
      return *this;
    }
    
    CFoo::CFoo(const CFoo& src)
    {
      s[0] = ID++;
      s[1] = 0;
      sprintf_s(s,1024,"%s ist Kopie von %s\r\n", s, src.s);
      printf("In Copy ctor: %s", s);
    }
    
    CFoo::CFoo(void)
    {
      s[0] = ID++;
      s[1] = 0;
      printf("In ctor: %s\r\n", s);
    }
    
    CFoo::~CFoo(void)
    {
      if (s[strlen(s)-1]!=10)
        printf("In dtor: %s\r\n", s);
      else
        printf("In dtor: %s", s);
    }
    
    int main(void)
    {
      CFoo a, b;
      CFoo c(a);
      CFoo d = a;
      b = a;
      printf("---------------------------\r\n");
      return 0;
    }
    

    Und das Ergebnis

    In ctor: A
    In ctor: B
    In Copy ctor: C ist Kopie von A
    In Copy ctor: D ist Kopie von A
    In Zuweisungsoperator: B wurde A zugewiesen
    ---------------------------
    In dtor: D ist Kopie von A
    In dtor: C ist Kopie von A
    In dtor: B wurde A zugewiesen
    In dtor: A
    

    Hätte ich das mal gleich gemacht! Wäre schneller vom Glauben zum Wissen gekommen...



  • Sodele, hab mal nachgelesen und soweit ich das ueberblicken kann, ruft Foo x = y immer den Copy-Ctor auf, unabhaengig vom Typ von y. Und zwar wird das, wenn ichs richtig verstanden hab wie folgt gehandhabt:
    - ist y vom Typ Foo, wird einfach der Copy-Ctor aufgerufen.
    - ist y vom Typ Bar und gibts einen non-explicit Konstructor a la Foo(const Bar& b) , so wird erst der Umwandlungskonstruktor und dann der Foo-Copy-Ctor aufgerufen (also ein temporaeres Objekt), im Endeffekt also Foo x = Foo(y) bzw. sowas wie Foo x(Foo(y))
    - ich hab nicht rausgefunden was passiert, wenns keinen Umwandlungskonstruktor gibt aber statt dessen einen Castoperator von Bar nach Foo. Ich Schaetze dass dann Foo x = (Foo)y Anwendung findet, wieder mit Copy-Ctor

    Kleines Schmankerl:
    Foo x(Bar()); wird uebrigens schiefgehn, auch wenns auf den ersten Blick nach einem ganz normalen Aufruf des Konvertierungskonstruktors aussieht. Aber: Der Compiler interpretiert das als Funktionisdeklaration (!), x ist dabei eine Funktion, die ein Foo liefert und als Argument eine argumentfreie Funktion erwartet, die ein Bar liefert. Also aequivalent zu

    typedef Bar Barfunc();
    Foo x(Barfunc b);
    

    Hier muss stattdessen also explizit der Copy-Ctor eingesetzt werden: Foo x = Foo(Bar()); oder Foo x = Bar(); , wenn der Konvertierungskonstruktor Foo(const Bar&) nonexplicit ist.



  • pumuckl schrieb:

    - ist y vom Typ Bar und gibts einen non-explicit Konstructor a la Foo(const Bar& b) , so wird erst der Umwandlungskonstruktor und dann der Foo-Copy-Ctor aufgerufen (also ein temporaeres Objekt), im Endeffekt also Foo x = Foo(y) bzw. sowas wie Foo x(Foo(y))

    Wobei man hier noch dazusagen müsste, dass der Compiler die Kopie in dem Fall vollständig wegoptimieren darf (und die meisten tun das), so dass letztendlich nur noch die direkte Konstruktion übrigbleibt. Trotzdem muss das Objekt die direkte Konstruktion und die Copy-Konstruktion erlauben, auch wenn zweitere durch Optimierungen wegfällt.


Anmelden zum Antworten