web-dev-qa-db-fra.com

Les variables statiques d'une classe de base sont-elles partagées par toutes les classes dérivées?

Si j'ai quelque chose comme

class Base {
    static int staticVar;
}

class DerivedA : public Base {}
class DerivedB : public Base {}

Est-ce que DerivedA et DerivedB partageront les mêmes staticVar ou auront-ils chacun la leur?

Si je voulais qu'ils aient chacun le leur, que recommanderiez-vous que je fasse?

50
mpen

Ils partageront chacun la même instance de staticVar.

Pour que chaque classe dérivée obtienne sa propre variable statique, vous devrez déclarer une autre variable statique avec un nom différent.

Vous pouvez ensuite utiliser une paire virtuelle de fonctions dans votre classe de base pour obtenir et définir la valeur de la variable, et remplacer cette paire dans chacune de vos classes dérivées pour obtenir et définir la variable statique "locale" pour cette classe. Alternativement, vous pouvez utiliser une seule fonction qui renvoie une référence:

class Base {
    static int staticVarInst;
public:
    virtual int &staticVar() { return staticVarInst; }
}
class Derived: public Base {
    static int derivedStaticVarInst;
public:
    virtual int &staticVar() { return derivedStaticVarInst; }
}

Vous utiliseriez alors ceci comme:

staticVar() = 5;
cout << staticVar();
43
Greg Hewgill

Pour vous assurer que chaque classe possède sa propre variable statique, vous devez utiliser "Modèle de modèle curieusement récurrent" (CRTP) .

template <typename T>
class Base
{
    static int staticVar;
};

template <typename T> int Base<T>::staticVar(0);

class DerivedA : public Base<DerivedA> {};
class DerivedB : public Base<DerivedB> {};
30
Mark Ingram

Ils partageront la même instance.

Vous devrez déclarer des variables statiques distinctes pour chaque sous-classe, ou vous pourriez envisager une carte statique simple dans laquelle vous pourriez stocker des variables référencées par des classes dérivées.


Edit: Une solution possible à cela serait de définir votre classe de base comme modèle. Avoir une variable statique définie dans ce modèle signifierait que chaque classe dérivée aura sa propre instance de la statique.

3
Alan

Il n'y a qu'un staticVar dans votre cas: Base::staticVar

Lorsque vous déclarez une variable statique dans une classe, la variable est déclarée pour cette classe uniquement. Dans votre cas, DerivedA ne peut même pas voir staticVar (car il est privé, non protégé ou public), donc il ne sait même pas qu'il existe une variable staticVar.

3
Reed Copsey

L'exemple de code donné par @einpoklum ne fonctionne pas tel quel en raison de l'initialisation manquante du membre statique foo_, héritage manquant dans la déclaration FooHolder et mots clés public manquants car nous avons affaire à des classes. En voici la version fixe.

#include <iostream>
#include <string>

class A {
public:
    virtual const int& Foo() const = 0;
};

template <typename T>
class FooHolder : public virtual A {
public:
    const int& Foo() const override { return foo_; }
    static int foo_;
};

class B : public virtual A, public FooHolder<B> { };
class C : public virtual A, public FooHolder<C> { };

template<>
int FooHolder<B>::foo_(0);
template<>
int FooHolder<C>::foo_(0);

int main()
{
  B b;
  C c;
  std::cout << b.Foo() << std::endl;
  std::cout << c.Foo() << std::endl;
}
1
milembar

Hélas, C++ n'a pas de membres de données statiques virtuelles. Il existe plusieurs façons de simuler cela, plus ou moins:

  • @ GregHewgill's solution vous fait répliquer la variable statique dans chaque classe dérivée; cette solution est simple, directe et n'introduit pas de classes supplémentaires, mais je n'aime pas celle-ci car elle est verbeuse, et vous devez être plutôt disciplinée avec elle.
  • @MarkIngram a suggéré une solution basée sur CRTP , qui vous évite la plupart de la frappe; cependant, cela gâche la structure d'héritage, car ce qui était auparavant des sous-classes de A n'est plus vraiment lié en tant que classes. Après tout, deux types de modèles avec le même nom mais des arguments de modèle différents peuvent être simplement deux types.

Je suggère une solution basée sur CRTP différente, en utilisant une classe mix-in :

 class A {
      virtual const int& Foo() const = 0;
 }

 template <typename T>
 class FooHolder {
      static int foo_;
      const int& Foo() const override { return foo_; }
 }

 class B : A, virtual FooHolder<B> { }

 class C : B, virtual FooHolder<B> { }

La seule chose que vous devez faire dans une sous-classe est également d'indiquer l'héritage mix-in. Il pourrait y avoir quelques mises en garde d'héritage virtuel que je manque ici (car je l'utilise rarement).

Notez que vous devez soit instancier et initialiser la variable statique de chaque sous-classe quelque part, soit en faire une variable inline (C++ 17) et l'initialiser dans le modèle.

Cette réponse a été adaptée de ma réponse à une question dupe .

1
einpoklum

Je sais que cette question a déjà été répondue, mais je voudrais fournir un petit exemple d'héritage avec des membres statiques. C'est une très belle façon de démontrer l'utilité ainsi que ce qui se passe avec les variables statiques et les constructeurs respectifs.

FooBase.h

#ifndef FOO_BASE_H
#define FOO_BASE_H

#include <string>

class FooBase {
protected:
    std::string _nameAndId;
private:
    std::string _id;
    static int _baseCounter;

public:
    std::string idOfBase();
    virtual std::string idOf() const = 0;

protected:
    FooBase();    
};

#endif // !FOO_BASE_H

FooBase.cpp

#include "FooBase.h"
#include <iostream>

int FooBase::_baseCounter = 0;

FooBase::FooBase() {
    _id = std::string( __FUNCTION__ ) + std::to_string( ++_baseCounter );
    std::cout << _id << std::endl;
}

std::string FooBase::idOfBase() {
    return _id;
}

std::string FooBase::idOf() const {
    return "";
} // empty

DerivedFoos.h

#ifndef DERIVED_FOOS_H
#define DERIVED_FOOS_H

#include "FooBase.h"

class DerivedA : public FooBase {
private:    
    static int _derivedCounter;

public:
    DerivedA();

    std::string idOf() const override;
};

class DerivedB : public FooBase {
private:
    static int _derivedCounter;

public:
    DerivedB();

    std::string idOf() const override;
};

#endif // !DERIVED_FOOS_H

DerivedFoos.cpp

#include "DerivedFoos.h"
#include <iostream>

int DerivedA::_derivedCounter = 0;
int DerivedB::_derivedCounter = 0;

DerivedA::DerivedA() : FooBase() {
    _nameAndId = std::string( __FUNCTION__ ) + std::to_string( ++DerivedA::_derivedCounter );
    std::cout << _nameAndId << std::endl;
}

std::string DerivedA::idOf() const {
    return _nameAndId;
}    

DerivedB::DerivedB() : FooBase() {
    _nameAndId = std::string( __FUNCTION__ ) + std::to_string( ++DerivedB::_derivedCounter );
    std::cout << _nameAndId << std::endl;
}

std::string DerivedB::idOf() const {
    return _nameAndId;
}

main.cpp

#include "DerivedFoos.h"

int main() {
    DerivedA a1;  
    DerivedA a2;
    DerivedB b1;
    DerivedB b2;

    system( "PAUSE" );
    return 0;
}

Si __FUNCTION__ ne fonctionne pas pour vous dans vos constructeurs, vous pouvez utiliser quelque chose de similaire qui peut le remplacer tel que __PRETTY_FUNCTION__ ou __func__, ou saisissez manuellement le nom de chaque classe :(.

1
Francis Cugler