Globale constante definiëren in C++

Ik wil een constante in C++ definiëren die zichtbaar is in verschillende bronbestanden.
Ik kan me de volgende manieren voorstellen om het in een headerbestand te definiëren:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. Sommige functies die de waarde retourneren (bijv. int get_GLOBAL_CONST_VAR())
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR;
    en in één bronbestand const int GLOBAL_CONST_VAR = 0xFF;

Optie (1) – is zeker niet de optie die u zou willen gebruiken

Optie (2) – het definiëren van de instantie van de variabele in elk objectbestand met behulp van het headerbestand

Optie (3) – IMO is in de meeste gevallen meer dan doden

Optie (4) – in veel gevallen misschien niet goed omdat enum geen concreet type heeft (C++0X zal de mogelijkheid toevoegen om het type te definiëren)

Dus in de meeste gevallen moet ik kiezen tussen (5) en (6).
Mijn vragen:

  1. Wat heb je liever (5) of (6)?
  2. Waarom is (5) oké, en (2) niet?

Antwoord 1, autoriteit 100%

Zeker met optie 5 gaan – het is type-veilig en stelt de compiler in staat om te optimaliseren (neem geen adres van die variabele 🙂 Ook als het in een header staat – plak het in een naamruimte om te voorkomen dat het globale bereik vervuilt:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants
} // namespace constants
// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;

Antwoord 2, autoriteit 49%

(5) zegt precies wat je wilt zeggen. Bovendien kan de compiler het meestal optimaliseren. (6) aan de andere kant laat de compiler het nooit optimaliseren omdat de compiler niet weet of je het uiteindelijk zult veranderen of niet.


Antwoord 3, autoriteit 35%

(5) is “beter” dan (6) omdat het GLOBAL_CONST_VARdefinieert als een integrale constante expressie (ICE) in alle vertaaleenheden. U kunt het bijvoorbeeld gebruiken als arraygrootte en als hoofdletterlabel in alle vertaaleenheden. In het geval van (6) zal GLOBAL_CONST_VARalleen een ICE zijn in die vertaaleenheid waar deze is gedefinieerd en alleen na het punt van definitie. In andere vertaaleenheden werkt het niet als ICE.

Houd er echter rekening mee dat (5) GLOBAL_CONST_VARinterne koppeling geeft, wat betekent dat de “adresidentiteit” van GLOBAL_CONST_VARin elke vertaaleenheid anders zal zijn, dwz de &GLOBAL_CONST_VARgeeft u een andere pointerwaarde in elke vertaaleenheid. In de meeste gebruiksgevallen maakt dit niet uit, maar als je een constant object nodig hebt met een consistente globale “adresidentiteit”, dan zou je voor (6) moeten gaan, waarbij je de ICE-ness van de constante in de proces.

Als de ICE-heid van de constante geen probleem is (geen integraal type) en de grootte van het type groter wordt (geen scalair type), dan wordt (6) meestal een betere benadering dan (5 ).

(2) is niet OK omdat de GLOBAL_CONST_VARin (2) standaard een externe koppeling heeft. Als je het in het headerbestand plaatst, krijg je meestal meerdere definities van GLOBAL_CONST_VAR, wat een fout is. const-objecten in C++ hebben standaard een interne koppeling, daarom werkt (5) (en daarom krijg je, zoals ik hierboven al zei, een aparte, onafhankelijke GLOBAL_CONST_VARin elke vertaaleenheid).


Vanaf C++17 heb je een optie om te declareren

inline extern const int GLOBAL_CONST_VAR = 0xFF;

in een headerbestand. Dit geeft u een ICE in alle vertaaleenheden (net als methode (5)) terwijl u tegelijkertijd de globale adresidentiteit van GLOBAL_CONST_VARbehoudt – in alle vertaaleenheden zal het hetzelfde adres hebben.


Antwoord 4, autoriteit 14%

Als je C++11 of later gebruikt, probeer dan compile-time constanten:

constexpr int GLOBAL_CONST_VAR{ 0xff };

Antwoord 5, autoriteit 7%

Als het een constante wordt, moet je het als een constante markeren – daarom is 2 naar mijn mening slecht.

De compiler kan de const-aard van de waarde gebruiken om een ​​deel van de wiskunde uit te breiden, en zelfs andere bewerkingen die de waarde gebruiken.

De keuze tussen 5 en 6 – hmm; 5 voelt gewoon beter voor mij.

In 6) wordt de waarde onnodig losgekoppeld van de declaratie.

Normaal gesproken zou ik een of meer van deze headers hebben die alleen constanten enz. erin definiëren, en dan geen andere ‘slimme’ dingen – mooie lichtgewicht headers die gemakkelijk overal kunnen worden opgenomen.


Antwoord 6, autoriteit 7%

Om je tweede vraag te beantwoorden:

(2) is illegaal omdat het de One Definition Rule schendt. Het definieert GLOBAL_CONST_VARin elk bestand waarin het is opgenomen, d.w.z. meer dan eens.
(5) is legaal omdat het niet onderworpen is aan de One Definition Rule. Elke GLOBAL_CONST_VARis een aparte definitie, lokaal voor dat bestand waar het is opgenomen. Al die definities hebben natuurlijk dezelfde naam en waarde, maar hun adressen kunnen verschillen.


Antwoord 7, autoriteit 5%

C++17 inlinevariabelen

Met deze geweldige C++17-functie kunnen we:

  • gebruik bij voorkeur slechts één geheugenadres voor elke constante
  • het opslaan als een constexpr: Hoe constexpr extern declareren?
  • doe het in een enkele regel uit één kop

main.cpp

#include <cassert>
#include "notmain.hpp"
int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
inline constexpr int notmain_i = 42;
const int* notmain_func();
#endif

notmain.cpp

#include "notmain.hpp"
const int* notmain_func() {
    return &notmain_i;
}

Compileren en uitvoeren:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub stroomopwaarts.

Zie ook: Hoe werken inline-variabelen?

C++-standaard voor inline-variabelen

De C++-standaard garandeert dat de adressen hetzelfde zijn. C++17 N4659 standaard concept
10.1.6 “De inline specificatie”:

6 Een inline-functie of variabele met externe koppeling moet hetzelfde adres hebben in alle vertaaleenheden.

cppreference https://en.cppreference.com/w/cpp/language/ inlinelegt uit dat als staticniet wordt gegeven, het een externe link heeft.

Inline variabele implementatie

We kunnen zien hoe het wordt geïmplementeerd met:

nm main.o notmain.o

die bevat:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i
notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

en man nmzegt over u:

“u” Het symbool is een uniek globaal symbool. Dit is een GNU-uitbreiding op de standaardset van ELF-symboolbindingen. Voor zo’n symbool zorgt de dynamische linker ervoor dat in het hele proces
er is slechts één symbool met deze naam en type in gebruik.

we zien dus dat hier een speciale ELF-extensie voor is.

Getest op GCC 7.4.0, Ubuntu 18.04.


Antwoord 8, autoriteit 3%

const int GLOBAL_CONST_VAR = 0xFF;

omdat het een constante is!


Antwoord 9

Het hangt af van uw vereisten. (5) is het beste voor het meest normale gebruik, maar resulteert vaak in het constant in beslag nemen van opslagruimte in elk objectbestand. (6) kan dit omzeilen in situaties waar het belangrijk is.

(4) is ook een goede keuze als uw prioriteit is om te garanderen dat opslagruimte nooit wordt toegewezen, maar het werkt natuurlijk alleen voor integrale constanten.


Antwoord 10

#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this

Other episodes