Ich bin heute auf ein interessantes Problem gestoßen. Betrachten Sie dieses einfache Beispiel:
template <typename T>
void foo(const T & a) { /* code */ }
// This would also fail
// void foo(const int & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
int main()
{
Bar b;
b.func();
}
Beim Kompilieren erhalte ich eine Fehlermeldung:
Undefined reference to 'Bar::kConst'
Ich bin mir ziemlich sicher, dass dies daran liegt, dass das static const int
nirgendwo definiert ist, was beabsichtigt ist, da der Compiler nach meinem Verständnis in der Lage sein sollte, das Ersetzen zur Kompilierungszeit vorzunehmen und keine Definition zu benötigen. Da die Funktion jedoch einen const int &
Parameter verwendet, scheint sie die Substitution nicht vorzunehmen und stattdessen eine Referenz zu bevorzugen. Ich kann dieses Problem beheben, indem ich folgende Änderung vornehme:
foo(static_cast<int>(kConst));
Ich glaube, dies zwingt den Compiler jetzt dazu, ein temporäres int zu erstellen und dann einen Verweis auf das zu übergeben, was er zur Kompilierungszeit erfolgreich ausführen kann.
Ich habe mich gefragt, ob dies beabsichtigt war oder erwarte ich zu viel von gcc, um diesen Fall behandeln zu können? Oder sollte ich das aus irgendeinem Grund nicht tun?
const int kConst = 1;
das gleiche Ergebnis erzielen. Es gibt auch selten einen Grund (ich kann mir keinen vorstellen), dass eine Funktion einen Parameter des Typs übernimmtconst int &
- verwenden Sie einfach einenint
hier.static
wird ein Fehler ausgegeben. "ISO C ++ verbietet die Initialisierung des Mitglieds 'kConst' ... macht 'kConst' statisch. 'std::min( some_val, kConst)
, da erstd::min<T>
Parameter vom Typ hatT const &
, und die Implikation ist, dass wir einen Verweis auf die kConst übergeben müssen. Ich fand es nur, wenn die Optimierung ausgeschaltet war. Mit statischer Besetzung behoben.Antworten:
Es ist beabsichtigt, 9.4.2 / 4 sagt:
Wenn Sie das statische Datenelement als const-Referenz übergeben, "verwenden" Sie es, 3.2 / 2:
Tatsächlich "verwenden" Sie es also, wenn Sie es auch als Wert übergeben, oder in a
static_cast
. Es ist nur so, dass GCC Sie in einem Fall vom Haken gelassen hat, im anderen nicht.[Bearbeiten: gcc wendet die Regeln aus C ++ 0x-Entwürfen an: "Eine Variable oder nicht überladene Funktion, deren Name als potenziell ausgewerteter Ausdruck angezeigt wird, wird odr-verwendet, es sei denn, es handelt sich um ein Objekt, das die Anforderungen für das Anzeigen in einer Konstanten erfüllt Ausdruck (5.19) und die Umwandlung von lWert in rWert (4.1) werden sofort angewendet. " Die statische Umwandlung führt sofort eine lvalue-rvalue-Konvertierung durch, sodass sie in C ++ 0x nicht "verwendet" wird.]
Das praktische Problem mit der const-Referenz besteht darin, dass sie berechtigt ist,
foo
die Adresse ihres Arguments zu übernehmen und sie beispielsweise mit der Adresse des Arguments eines anderen Aufrufs zu vergleichen, der in einem globalen Aufruf gespeichert ist. Da ein statisches Datenelement ein eindeutiges Objekt ist, bedeutet dies, dass bei einem Aufruffoo(kConst)
von zwei verschiedenen TUs die Adresse des übergebenen Objekts jeweils gleich sein muss. AFAIK GCC kann dies nur arrangieren, wenn das Objekt in einer (und nur einer) TU definiert ist.OK, in diesem Fall
foo
handelt es sich also um eine Vorlage, daher ist die Definition in allen TUs sichtbar. Vielleicht könnte der Compiler theoretisch das Risiko ausschließen, dass er irgendetwas mit der Adresse macht. Aber im Allgemeinen sollten Sie sicherlich keine Adressen oder Verweise auf nicht vorhandene Objekte nehmen ;-)quelle
template <int N> int intvalue() { return N; }
. Dann mitintvalue<kConst>
,kConst
erscheint nur in einem Kontext einen integralen konstanten Ausdruck erfordert, und so wird nicht verwendet. Die Funktion gibt jedoch eine temporäre Datei mit demselben Wert wie zurückkConst
, die an eine const-Referenz gebunden werden kann. Ich bin mir jedoch nicht sicher, ob es einen einfacheren Weg zur tragbaren DurchsetzungkConst
gibt, der nicht verwendet wird.r = s ? kConst1 : kConst2
) mit gcc 4.7 verwende. Ich habe es mit einem tatsächlichen gelöstif
. Trotzdem danke für die Antwort!Wenn Sie eine statische const-Variable mit dem Initialisierer innerhalb der Klassendeklaration schreiben, ist es so, als hätten Sie geschrieben
class Bar { enum { kConst = 1 }; }
und GCC wird es genauso behandeln, was bedeutet, dass es keine Adresse hat.
Der richtige Code sollte sein
class Bar { static const int kConst; } const int Bar::kConst = 1;
quelle
Dies ist ein wirklich gültiger Fall. Vor allem, weil foo eine Funktion aus der STL wie std :: count sein könnte, die ein const T & als drittes Argument verwendet.
Ich habe viel Zeit damit verbracht zu verstehen, warum der Linker Probleme mit einem so grundlegenden Code hatte.
Die Fehlermeldung
sagt uns, dass der Linker kein Symbol finden kann.
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 U Bar::kConst
Wir können am 'U' erkennen, dass Bar :: kConst undefiniert ist. Wenn der Linker versucht, seine Arbeit zu erledigen, muss er daher das Symbol finden. Aber nur du deklarieren jedoch kConst und definieren es nicht.
Die Lösung in C ++ besteht auch darin, es wie folgt zu definieren:
template <typename T> void foo(const T & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; const int Bar::kConst; // Definition <--FIX int main() { Bar b; b.func(); }
Dann können Sie sehen, dass der Compiler die Definition in die generierte Objektdatei einfügt:
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 R Bar::kConst
Jetzt können Sie das 'R' sehen, das besagt, dass es im Datenabschnitt definiert ist.
quelle
>nm -C main.o | grep kConst
gibt mir nur eine Zeile0000000000400644 R Bar::kConst
.nm -C lib.a
gibt IhnenConstants.o: 0000000000000000 R Bar::kConst
undmain_file.o: U Bar::kConst ...
.g ++ Version 4.3.4 akzeptiert diesen Code (siehe diesen Link ). Aber g ++ Version 4.4.0 lehnt es ab.
quelle
Ich denke, dieses Artefakt von C ++ bedeutet, dass das jederzeit möglich ist
Bar::kConst
verwiesen wird, stattdessen sein Literalwert verwendet wird.Dies bedeutet, dass es in der Praxis keine Variable gibt, auf die Bezug genommen werden kann.
Möglicherweise müssen Sie dies tun:
void func() { int k = kConst; foo(k); }
quelle
foo(static_cast<int>(kConst));
, oder?Sie können es auch durch eine constexpr-Mitgliedsfunktion ersetzen:
class Bar { static constexpr int kConst() { return 1; }; };
quelle
Einfacher Trick: Verwenden Sie
+
vor derkConst
Weitergabe der Funktion. Dadurch wird verhindert, dass der Konstante eine Referenz entnommen wird. Auf diese Weise generiert der Code keine Linkeranforderung für das Konstantenobjekt, sondern setzt stattdessen den Konstantenwert für die Compilerzeit fort.quelle
static const
Wert übernommen wird, der bei der Deklaration initialisiert wird. Dies würde immer zu einem Linkerfehler führen, und wenn dieselbe Konstante auch in einer Objektdatei separat deklariert wird, wäre dies ebenfalls ein Fehler. Der Compiler ist sich auch der Situation voll bewusst.static_cast<decltype(kConst)>(kConst)
.foo()
ursprüngliche Frage die Adresse, und es gibt keinen Mechanismus, um sie als temporäre Kopie des gesamten Arrays zu behandeln.Ich hatte das gleiche Problem wie von Cloderic (statische Konstante in einem ternären Operator :)
r = s ? kConst1 : kConst2
, aber es hat sich erst nach dem Deaktivieren der Compileroptimierung beschwert (-O0 statt -Os). Passiert auf gcc-none-eabi 4.8.5.quelle