2008-09-11 9 views
8

Ich habe ein wenig Mühe herauszufinden, genau wie const in einem bestimmten Fall gilt. Hier ist der Code, den ich habe:Const Struct &

struct Widget 
{ 
    Widget():x(0), y(0), z(0){} 

    int x, y, z; 
}; 

struct WidgetHolder //Just a simple struct to hold four Widgets. 
{ 
    WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){} 

    Widget& A; 
    Widget& B; 
    Widget& C; 
    Widget& D; 
}; 

class Test //This class uses four widgets internally, and must provide access to them externally. 
{ 
    public: 
     const WidgetHolder AccessWidgets() const 
     { 
      //This should return our four widgets, but I don't want anyone messing with them. 
      return WidgetHolder(A, B, C, D); 
     } 

     WidgetHolder AccessWidgets() 
     { 
      //This should return our four widgets, I don't care if they get changed. 
      return WidgetHolder(A, B, C, D); 
     } 

    private: 
     Widget A, B, C, D; 
}; 

int main() 
{ 
    const Test unchangeable; 

    unchangeable.AccessWidgets().A.x = 1; //Why does this compile, shouldn't the Widget& be const? 
} 

Grundsätzlich habe ich eine Klasse namens Test. Es verwendet intern vier Widgets, und ich brauche es, um diese zurückzugeben, aber wenn Test const deklariert wurde, möchte ich, dass die Widgets auch const zurückgegeben werden.

Kann mir jemand erklären, warum der Code in main() kompiliert?

Vielen Dank.

Antwort

2

Dies kompiliert, da obwohl der WidgetHolder ein const-Objekt ist, diese Const-Ness nicht automatisch auf Objekte angewendet wird, auf die der WidgetHolder verweist. Stellen Sie sich das auf einer Maschinenebene vor - wenn das WidgetHolder-Objekt selbst im schreibgeschützten Speicher gehalten würde, könnten Sie immer noch in Dinge schreiben, auf die der WidgetHolder zeigte.

Das Problem scheint in dieser Linie zu liegen:

WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){} 

Wie Frank erwähnt, Ihre Referenzen in der WidgetHolder Klasse ungültige Referenzen nach dem Konstruktor kehrt halten werden. Daher sollten Sie dies ändern:

WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){} 

Nachdem Sie das tun, wird es nicht kompilieren, und ich lasse es als eine Übung für den Leser, den Rest der Lösung zu erarbeiten.

3

unchangeable.AccessWidgets():

An dieser Stelle erstellen Sie ein neues Objekt vom Typ WidgetHolder. Dieses Objekt ist nicht durch const geschützt.

Sie erstellen auch neue Widgets im WidgetHolder und keine Verweise auf das Wdiget.

+0

Neue Widgets werden nicht in WidgetHolder erstellt werden; Die Member sind Referenzen, aber die Konstruktorargumente sind Kopien. –

+0

Patrick du irrst dich. Der Konstruktor erstellt neue Widgets, und die Referenzen sind Verweise auf diese neuen Widgets. Er erhält keine Referenzen zu seinen Original-Widgets. –

3

Ihre WidgetHolder enthält ungültige Referenzen (Zeiger). Sie übergeben Objekte auf dem Stapel an den Konstruktor und halten dann Verweise auf ihre (temporären) Adressen. Dies ist garantiert zu brechen.

Sie sollten nur Referenzen auf Objekte mit derselben (oder längeren) Lebensdauer wie die Referenz selbst zuweisen.

Übergeben Sie Verweise auf den Konstruktor, wenn Sie Referenzen halten müssen. Noch besser, halte die Referenzen nicht und mache nur die Kopien.

+0

Alternativ ist es auch möglich, Zeiger (nicht Verweise) zu übergeben, wenn Sie explizite Garantien für die Lebensdauer und den Besitz dieser Objekte geben. Wenn Sie Verweise übergeben, kann WidgetHolder die Objekte nicht "besitzen". – hazzen

8

Sie müssen einen neuen Typ speziell für das Halten von const Widget & Objekten erstellen. Dh:

 

struct ConstWidgetHolder 
{ 
    ConstWidgetHolder(const Widget &a, const Widget &b, const Widget &c, const Widget &d): A(a), B(b), C(c), D(d){} 

    const Widget& A; 
    const Widget& B; 
    const Widget& C; 
    const Widget& D; 
}; 

class Test 
{ 
public: 
    ConstWidgetHolder AccessWidgets() const 
    { 
     return ConstWidgetHolder(A, B, C, D); 
    } 
 

Sie werden jetzt die folgende Fehlermeldung (in gcc 4.3):

 
widget.cc: In function 'int main()': 
widget.cc:51: error: assignment of data-member 'Widget::x' in read-only structure 

Ein ähnliches Idiom in der Standardbibliothek verwendet wird, mit Iteratoren dh:

 

class vector { 
    iterator begin(); 
    const_iterator begin() const; 
 
0

EDIT : Er löschte seine Antwort und ließ mich ein bisschen dumm aussehen :)

Die Antwort von Flame ist gefährlich falsch.Sein WidgetHolder nimmt Bezug auf ein Wertobjekt im Konstruktor. Sobald der Konstruktor zurückgegeben wird, wird das übergebene Wertobjekt zerstört und Sie erhalten einen Verweis auf ein zerstörtes Objekt.

Eine sehr einfache Beispielanwendung seinen Code zeigt dies deutlich:

#include <iostream> 

class Widget 
{ 
    int x; 
public: 
    Widget(int inX) : x(inX){} 
    ~Widget() { 
    std::cout << "widget " << static_cast< void*>(this) << " destroyed" << std::endl; 
    } 
}; 

struct WidgetHolder 
{ 
    Widget& A; 

public: 
    WidgetHolder(Widget a): A(a) {} 

    const Widget& a() const { 
    std::cout << "widget " << static_cast< void*>(&A) << " used" << std::endl; 
    return A; 
} 

}; 

int main(char** argv, int argc) 
{ 
Widget test(7); 
WidgetHolder holder(test); 
Widget const & test2 = holder.a(); 

return 0; 
} 

Der Ausgang so etwas wie

 
widget 0xbffff7f8 destroyed 
widget 0xbffff7f8 used 
widget 0xbffff7f4 destroyed 

dies der WidgetHolder Konstruktor sollte es auf die Variablen Referenzen nehmen zu vermeiden wäre, will als Referenzen speichern.

0

Die ursprüngliche Abfrage war, wie der WidgetHolder als const zurückgegeben wird, wenn die enthaltende Klasse const war. C++ verwendet const als Teil der Funktionssignatur und daher können Sie const und none const Versionen der gleichen Funktion haben. Die none const one wird aufgerufen, wenn die Instanz keine Konstante ist, und die Konstante const wird aufgerufen, wenn die Instanz const ist. Daher besteht eine Lösung darin, auf die Widgets im Widget-Halter anhand von Funktionen und nicht direkt zuzugreifen. Ich habe ein einfacheres Beispiel erstellt, von dem ich glaube, dass es die ursprüngliche Frage beantwortet.

#include <stdio.h> 

class Test 
{ 
public: 
    Test(int v){m_v = v;} 
~Test(){printf("Destruct value = %d\n",m_v);} 

int& GetV(){printf ("None Const returning %d\n",m_v); return m_v; } 

const int& GetV() const { printf("Const returning %d\n",m_v); return m_v;} 
private: 
    int m_v; 
}; 

void main() 
{ 
    // A none const object (or reference) calls the none const functions 
    // in preference to the const 
    Test one(10); 
    int& x = one.GetV(); 
    // We can change the member variable via the reference 
    x = 12; 

    const Test two(20); 
    // This will call the const version 
    two.GetV(); 

    // So the below line will not compile 
    // int& xx = two.GetV(); 

    // Where as this will compile 
    const int& xx = two.GetV(); 

    // And then the below line will not compile 
    // xx = 3; 

} 

In Bezug auf den ursprünglichen Code, ich denke, es wäre einfacher, eine WidgetHolder als Mitglied der Klasse-Test zu haben, und dann entweder eine konstante oder keine konstante Referenz darauf zurück, und die Widgets private Mitglieder machen des Halters und stellen einen const und keinen const Accessor für jedes Widget zur Verfügung.

class WidgetHolder { 
... 

Widget& GetA(); 
const Widget& GetA() const; 
... 
}; 

Und dann auf der Hauptklasse

class Test { 
... 
WigetHolder& AccessWidgets() { return m_Widgets;} 
const WidgetHolder&AcessWidgets() const { return m_Widgets;} 

private: 
    WidgetHolder m_Widgets; 
... 
};