2016-12-16 1 views
0

Je m'excuse si le titre n'est pas entièrement auto-explicatif. J'essaye de comprendre pourquoi mon modèle d'usine de singleton ne fonctionne pas correctement, et je me suis heurté à une différence bizarre en utilisant la bibliothèque contre la liaison des fichiers d'objets simples.Singleton à travers les unités de compilation: reliant la bibliothèque vs les objets de liaison

est ici une version simplifiée du code:

main.cpp

#include <iostream> 
#include "bar.hpp" 

int main (int /*argc*/, char** /*argv*/) 
{ 
    A::get().print(); 
    return 0; 
} 

bar.hpp

#ifndef BAR_HPP 
#define BAR_HPP 

#include <iostream> 

class A 
{ 
public: 
    static A& get() 
    { 
    static A a; 
    return a; 
    } 
    bool set(const int i) 
    { 
    m_i = i; 
    print(); 
    return true; 
    } 
    void print() 
    { 
    std::cout << "print: " << m_i << "\n"; 
    } 
private: 

    int m_i; 
    A() : m_i(0) {} 
}; 
#endif // BAR_HPP 

baz.hpp

#ifndef BAZ_HPP 
#define BAZ_HPP 

#include "bar.hpp" 

namespace 
{ 
static bool check = A::get().set(2); 
} 

#endif // BAZ_HPP 

baz.cpp

#include "baz.hpp" 

Maintenant, je construis mon "projet" de deux façons:

Makefile:

all: 
    g++ -std=c++11 -c baz.cpp 
    g++ -std=c++11 -o test main.cpp baz.o 
lib: 
    g++ -std=c++11 -c baz.cpp 
    ar rvs mylib.a baz.o 
    g++ -std=c++11 -o test main.cpp mylib.a 

Voici les sorties que je reçois:

$ make all 
$ ./test 
print: 2 
print: 2 

$ make lib 
$ ./test 
print: 0 

Dans le premier cas l'appel à A::get().set(2) dans baz.hpp a lieu, et la même instanciation de A est alors utilisée dans la fonction principale, qui imprime donc 2. Dans le second cas, l'appel à A::get().set(2) dans baz.hpp n'a jamais lieu, et dans la fonction principale, la valeur définie par le constructeur (c'est-à-dire 0) est imprimée.

Donc finalement je peux poser ma question: pourquoi le comportement différent dans les deux cas? Je m'attendrais à ce que les deux impriment une fois 0 ou impriment deux fois. J'ai toujours supposé qu'une bibliothèque était juste un moyen compact d'expédier des fichiers objets, et que le comportement de la liaison de mylib.a devrait être le même que celui de relier baz.o directement. Pourquoi n'est-ce pas le cas?

Éditer: la raison, comme expliqué par Richard, est qu'aucun des symboles définis dans baz.cpp n'est requis dans main.cpp, donc baz.o n'est pas extrait de la bibliothèque et lié. Cela soulève une autre question: existe-t-il une solution de contournement pour s'assurer que l'instruction A::get().set(2) est exécutée? Je voudrais éviter de faire du singleton un objet global, mais je ne suis pas sûr que ce soit possible. Je voudrais également éviter d'inclure baz.hpp dans le principal, car il peut y avoir beaucoup de bazxyz.hpp et cela nécessiterait main.cpp d'en savoir à l'avance tous, défiant tout le but du processus d'enregistrement de type usine ...

+3

rien dans main.cpp exige un symbole dans baz.cpp, de sorte que le baz archivé.o ne sera pas lié à l'exécutable –

+1

La liaison du fichier objet force-t-elle le lien à la place? Cela a du sens, je suppose. – bartgol

+0

oui, lier explicitement le fichier objet le force dans l'exécutable. –

Répondre

1

Si cela doit être une bibliothèque statique, certains modules va quelque part devoir répondre à quelque chose dans chaque fichier de mise en œuvre des objets qui vont s'inscrire à l'usine.

Un endroit raisonnable pour ce serait dans bar.cpp (qui est un fichier que vous n'avez pas encore). Il contiendrait tout ou partie de l'implémentation de A plus quelques moyens d'appeler les fonctions d'enregistrement les widgets que vous allez créer.

auto-découverte ne fonctionne que si les fichiers objets sont liés dans l'exécutable. Cela donne à la séquence de démarrage C++ une chance de connaître et de construire tous les objets avec un lien global.

+0

C'est ce que je finis par faire. Cela m'ennuie que dans bar.cpp je doive inclure tous les en-têtes pour tous les bazxyz.hpp, mais il semble qu'il n'y a aucun moyen de contourner le problème. En particulier, cela me dérange que quand un nouveau bazxyz.hpp est créé, alors le développeur doit se souvenir d'ajouter son include dans le fichier bar.cpp. Ce comportement changerait-il avec une bibliothèque dynamique? Pas que cela fasse une différence pour moi, puisque je veux laisser le choix à l'utilisateur, mais par curiosité. – bartgol

+0

@bartgol vous n'avez pas nécessairement besoin d'inclure les en-têtes - juste la déclaration externe d'une fonction dans le fichier cpp ... –

+0

Eh bien, c'est toujours quelque chose que j'ai besoin d'ajouter à bar.cpp chaque fois qu'un nouveau bazxyz.hpp est créé. Je suppose que je voulais avoir mon gâteau et le manger aussi ... – bartgol