web-dev-qa-db-ja.com

C ++静的仮想メンバー?

C++では、staticvirtualの両方のメンバー関数を使用できますか?どうやらそれを行う簡単な方法はありませんが(static virtual member();はコンパイルエラーです)、少なくとも同じ効果を達成する方法はありますか?

I.E:

struct Object
{
     struct TypeInformation;

     static virtual const TypeInformation &GetTypeInformation() const;
};

struct SomeObject : public Object
{
     static virtual const TypeInformation &GetTypeInformation() const;
};

インスタンス(GetTypeInformation())とクラス(object->GetTypeInformation())の両方でSomeObject::GetTypeInformation()を使用するのは理にかなっています。これは比較に役立ち、テンプレートに不可欠です。

考えられる唯一の方法は、クラスごとに2つの関数/関数と定数を記述するか、マクロを使用することです。

他のソリューションはありますか?

130
cvb

いいえ、それを行う方法はありません。Object::GetTypeInformation()を呼び出したときに何が起こるでしょうか?オブジェクトが関連付けられていないため、どの派生クラスのバージョンを呼び出すかを知ることはできません。

適切に動作させるには、非静的仮想関数にする必要があります。オブジェクトインスタンスを使用せずに特定の派生クラスのバージョンを非仮想的に呼び出すこともできるようにする場合は、2番目の冗長な静的非仮想バージョンも提供する必要があります。

73
Adam Rosenfield

多くの人がそれは不可能だと言います、私はさらに一歩進んで、それは意味がないと言います。

静的メンバーは、クラスにのみ関連し、インスタンスには関連しないものです。

仮想メンバーは、クラスに直接関係せず、インスタンスにのみ関係するものです。

そのため、静的仮想メンバーは、インスタンスやクラスに関連しないものになります。

55
Rasmus Kaj

先日、この問題に遭遇しました。静的メソッドでいっぱいのクラスがいくつかありましたが、継承メソッドと仮想メソッドを使用して、コードの繰り返しを減らしたいと思いました。私の解決策は:

静的メソッドを使用する代わりに、仮想メソッドでシングルトンを使用します。

つまり、各クラスには、クラスの単一の共有インスタンスへのポインターを取得するために呼び出す静的メソッドが含まれている必要があります。真のコンストラクターをプライベートまたは保護して、外部のコードが追加のインスタンスを作成することでそれを悪用できないようにすることができます。

実際には、シングルトンを使用することは、静的メソッドを使用することとよく似ていますが、継承メソッドと仮想メソッドを利用できる点が異なります。

21
Nate C-K

可能です!

しかし、正確に可能なことは、絞り込みましょう。静的呼び出し「SomeDerivedClass :: myfunction()」と多態的な呼び出し「base_class_pointer-> myfunction()」を介して同じ関数を呼び出すことができるために必要なコードの重複のため、人々はしばしばある種の「静的仮想関数」を必要とします。そのような機能を許可する「合法的な」方法は、関数定義の複製です。

class Object
{
public:
    static string getTypeInformationStatic() { return "base class";}
    virtual string getTypeInformation() { return getTypeInformationStatic(); }
}; 
class Foo: public Object
{
public:
    static string getTypeInformationStatic() { return "derived class";}
    virtual string getTypeInformation() { return getTypeInformationStatic(); }
};

基本クラスに多数の静的関数があり、派生クラスがそれらのすべてをオーバーライドする必要があり、仮想関数の重複定義を提供するのを忘れた場合はどうなるでしょう。右、runtimeの間に奇妙なエラーが発生しますが、これは追跡するのが困難です。コードの重複は悪いことです。以下はこの問題を解決しようとします(そして、それは完全にタイプセーフであり、typeidやdynamic_castのような黒魔術を含まないことを事前に伝えたいです:)

したがって、派生クラスごとにgetTypeInformation()の定義を1つだけ提供したいのですが、「static functionの定義でなければならないことは明らかです。 "SomeDerivedClass :: getTypeInformation( ) "getTypeInformation()が仮想の場合。基底クラスへのポインターを介して派生クラスの静的関数を呼び出すにはどうすればよいですか? vtableは仮想関数へのポインタのみを格納し、仮想関数を使用しないことにしたため、vtableを変更することはできません。vtableでは不可能です。次に、基本クラスへのポインターを介して派生クラスの静的関数にアクセスできるようにするには、何らかの方法でその基本クラス内にオブジェクトの型を格納する必要があります。 1つのアプローチは、「不思議な繰り返しテンプレートパターン」を使用して基本クラスをテンプレート化することですが、ここでは適切ではなく、「型消去」と呼ばれる手法を使用します。

class TypeKeeper
{
public:
    virtual string getTypeInformation() = 0;
};
template<class T>
class TypeKeeperImpl: public TypeKeeper
{
public:
    virtual string getTypeInformation() { return T::getTypeInformationStatic(); }
};

これで、変数「キーパー」を使用して、基本クラス「オブジェクト」内にオブジェクトのタイプを保存できます。

class Object
{
public:
    Object(){}
    boost::scoped_ptr<TypeKeeper> keeper;

    //not virtual
    string getTypeInformation() const 
    { return keeper? keeper->getTypeInformation(): string("base class"); }

};

派生クラスでは、構築中にキーパーを初期化する必要があります。

class Foo: public Object
{
public:
    Foo() { keeper.reset(new TypeKeeperImpl<Foo>()); }
    //note the name of the function
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

構文糖を追加しましょう:

template<class T>
void override_static_functions(T* t)
{ t->keeper.reset(new TypeKeeperImpl<T>()); }
#define OVERRIDE_STATIC_FUNCTIONS override_static_functions(this)

子孫の宣言は次のようになります。

class Foo: public Object
{
public:
    Foo() { OVERRIDE_STATIC_FUNCTIONS; }
    static string getTypeInformationStatic() 
    { return "class for proving static virtual functions concept"; }
};

class Bar: public Foo
{
public:
    Bar() { OVERRIDE_STATIC_FUNCTIONS; }
    static string getTypeInformationStatic() 
    { return "another class for the same reason"; }
};

使用法:

Object* obj = new Foo();
cout << obj->getTypeInformation() << endl;  //calls Foo::getTypeInformationStatic()
obj = new Bar();
cout << obj->getTypeInformation() << endl;  //calls Bar::getTypeInformationStatic()
Foo* foo = new Bar();
cout << foo->getTypeInformation() << endl; //calls Bar::getTypeInformationStatic()
Foo::getTypeInformation(); //compile-time error
Foo::getTypeInformationStatic(); //calls Foo::getTypeInformationStatic()
Bar::getTypeInformationStatic(); //calls Bar::getTypeInformationStatic()

利点:

  1. コードの重複が少ない(ただし、すべてのコンストラクターでOVERRIDE_STATIC_FUNCTIONSを呼び出す必要があります)

短所:

  1. すべてのコンストラクターのOVERRIDE_STATIC_FUNCTIONS
  2. メモリとパフォーマンスのオーバーヘッド
  3. 複雑さの増加

未解決の問題:

1)ここで曖昧さを解決する方法は、静的関数と仮想関数には異なる名前がありますか?

class Foo
{
public:
    static void f(bool f=true) { cout << "static";}
    virtual void f() { cout << "virtual";}
};
//somewhere
Foo::f(); //calls static f(), no ambiguity
ptr_to_foo->f(); //ambiguity

2)すべてのコンストラクタ内でOVERRIDE_STATIC_FUNCTIONSを暗黙的に呼び出す方法は?

14
Alsk

Alskは既にかなり詳細な回答を提供していますが、代替実装を追加したいと思います。彼の拡張された実装は複雑すぎると思うからです。

すべてのオブジェクトタイプのインターフェイスを提供する抽象基本クラスから始めます。

class Object
{
public:
    virtual char* GetClassName() = 0;
};

次に、実際の実装が必要です。ただし、静的メソッドと仮想メソッドの両方を記述する必要がないように、実際のオブジェクトクラスに仮想メソッドを継承させます。基本クラスが静的メンバー関数にアクセスする方法を知っている場合、これは明らかに機能します。そのため、テンプレートを使用して、実際のオブジェクトクラス名を渡す必要があります。

template<class ObjectType>
class ObjectImpl : public Object
{
public:
    virtual char* GetClassName()
    {
        return ObjectType::GetClassNameStatic();
    }
};

最後に、実際のオブジェクトを実装する必要があります。ここでは、静的メンバー関数のみを実装する必要があります。仮想メンバー関数はObjectImplテンプレートクラスから継承され、派生クラスの名前でインスタンス化されるため、静的メンバーにアクセスします。

class MyObject : public ObjectImpl<MyObject>
{
public:
    static char* GetClassNameStatic()
    {
        return "MyObject";
    }
};

class YourObject : public ObjectImpl<YourObject>
{
public:
    static char* GetClassNameStatic()
    {
        return "YourObject";
    }
};

テストするコードを追加してみましょう。

char* GetObjectClassName(Object* object)
{
    return object->GetClassName();
}

int main()
{
    MyObject myObject;
    YourObject yourObject;

    printf("%s\n", MyObject::GetClassNameStatic());
    printf("%s\n", myObject.GetClassName());
    printf("%s\n", GetObjectClassName(&myObject));
    printf("%s\n", YourObject::GetClassNameStatic());
    printf("%s\n", yourObject.GetClassName());
    printf("%s\n", GetObjectClassName(&yourObject));

    return 0;
}

補遺(2019年1月12日):

GetClassNameStatic()関数を使用する代わりに、クラス名を「インライン」でさえ静的メンバーとして定義することもできます。IIRCはC++ 11以降で機能します(すべての修飾子に怖がらないでください:))。

class MyObject : public ObjectImpl<MyObject>
{
public:
    // Access this from the template class as `ObjectType::s_ClassName` 
    static inline const char* const s_ClassName = "MyObject";

    // ...
};
12
Timo

可能です。静的と仮想の2つの機能を作成する

struct Object{     
  struct TypeInformation;
  static  const TypeInformation &GetTypeInformationStatic() const 
  { 
      return GetTypeInformationMain1();
  }
  virtual const TypeInformation &GetTypeInformation() const
  { 
      return GetTypeInformationMain1();
  }
protected:
  static const TypeInformation &GetTypeInformationMain1(); // Main function
};

struct SomeObject : public Object {     
  static  const TypeInformation &GetTypeInformationStatic() const 
  { 
      return GetTypeInformationMain2();
  }
  virtual const TypeInformation &GetTypeInformation() const
  { 
      return GetTypeInformationMain2();
  }
protected:
  static const TypeInformation &GetTypeInformationMain2(); // Main function
};
11
Alexey Malistov

いいえ、静的メンバー関数にはthisポインターがないため、これは不可能です。また、静的メンバー(関数と変数の両方)は、実際にはクラスメンバーではありません。それらはたまたまClassName::memberによって呼び出され、クラスアクセス指定子に準拠しています。それらのストレージはクラス外のどこかで定義されます。クラスのオブジェクトをインスタンス化するたびにストレージが作成されるわけではありません。クラスメンバへのポインタは、セマンティクスと構文において特別です。静的メンバーへのポインターは、あらゆる点で通常のポインターです。

クラス内の仮想関数はthisポインターを必要とし、クラスと非常に結合しているため、静的にすることはできません。

8
Mads Elvheim

まあ、かなり遅い答えですが、不思議な繰り返しテンプレートパターンを使用することは可能です。この wikipedia の記事には必要な情報があり、静的多態性の例も求められています。

6
tropicana

いいえ、静的メンバー関数を仮想にすることはできません。仮想コンセプトは実行時にvptrの助けを借りて解決されるため、vptrはクラスの非静的メンバーです。そのため、静的メンバー関数はvptrにアクセスできないため、静的メンバーは仮想ではありません。

3
Prabhat Kumar

あなたがやろうとしていることはテンプレートを通してできると思います。ここで行間を読み込もうとしています。あなたがやろうとしているのは、いくつかのコードからメソッドを呼び出すことです。ここでは、派生バージョンを呼び出しますが、呼び出し元はどのクラスを指定しません。例:

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

void Try()
{
    xxx::M();
}

int main()
{
    Try();
}

Try()で、Barを指定せずにMのBarバージョンを呼び出すようにします。これを静的に行う方法は、テンプレートを使用することです。次のように変更します。

class Foo {
public:
    void M() {...}
};

class Bar : public Foo {
public:
    void M() {...}
};

template <class T>
void Try()
{
    T::M();
}

int main()
{
    Try<Bar>();
}
2
zumalifeguard

静的メンバーはコンパイル時にバインドされ、仮想メンバーは実行時にバインドされるため、できません。

0
PaulJWilliams

まず、OPが要求していることは用語の矛盾であるという回答は正しいです。仮想メソッドはインスタンスの実行時の型に依存します。静的関数は、具体的にはインスタンスに依存せず、型にのみ依存します。そうは言っても、静的関数が型に固有の何かを返すようにすることは理にかなっています。たとえば、Stateパターン用のMouseToolクラスのファミリがあり、各クラスにそれに伴うキーボード修飾子を返す静的関数を持たせるようになりました。正しいMouseToolインスタンスを作成するファクトリー関数でこれらの静的関数を使用しました。この関数は、MouseToolA :: keyboardModifier()、MouseToolB :: keyboardModifier()などに対してマウスの状態をチェックし、適切なものをインスタンス化しました。もちろん後で状態が正しいかどうかを確認したかったので、「if(keyboardModifier == dynamic_type(* state):: keyboardModifier())」(実際のC++構文ではない)のようなものを書きたいと思っていました。 。

したがって、これが必要な場合は、ソリューションを修正する必要があります。それでも、静的メソッドを使用して、インスタンスの動的タイプに基づいて動的メソッドを呼び出すという要望を理解しています。 Visitor Patternはあなたが望むものを提供できると思います。それはあなたが望むものを提供します。それは少し余分なコードですが、他の訪問者にとっては有用かもしれません。

参照: http://en.wikipedia.org/wiki/Visitor_pattern 背景について。

struct ObjectVisitor;

struct Object
{
     struct TypeInformation;

     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v);
};

struct SomeObject : public Object
{
     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v) const;
};

struct AnotherObject : public Object
{
     static TypeInformation GetTypeInformation();
     virtual void accept(ObjectVisitor& v) const;
};

次に、具体的なオブジェクトごとに:

void SomeObject::accept(ObjectVisitor& v) const {
    v.visit(*this); // The compiler statically picks the visit method based on *this being a const SomeObject&.
}
void AnotherObject::accept(ObjectVisitor& v) const {
    v.visit(*this); // Here *this is a const AnotherObject& at compile time.
}

そして、ベースビジターを定義します:

struct ObjectVisitor {
    virtual ~ObjectVisitor() {}
    virtual void visit(const SomeObject& o) {} // Or = 0, depending what you feel like.
    virtual void visit(const AnotherObject& o) {} // Or = 0, depending what you feel like.
    // More virtual void visit() methods for each Object class.
};

次に、適切な静的関数を選択する具体的な訪問者:

struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject& o) {
        result = SomeObject::GetTypeInformation();
    }
    virtual void visit(const AnotherObject& o) {
        result = AnotherObject::GetTypeInformation();
    }
    // Again, an implementation for each concrete Object.
};

最後に、それを使用します:

void printInfo(Object& o) {
    ObjectVisitorGetTypeInfo getTypeInfo;
    Object::TypeInformation info = o.accept(getTypeInfo).result;
    std::cout << info << std::endl;
}

ノート:

  • 練習として残された誠実さ。
  • 静的から参照を返しました。シングルトンがない限り、それは疑問です。

訪問メソッドの1つが間違った静的関数を呼び出すコピーアンドペーストエラーを回避したい場合は、次のようなテンプレートで訪問者にテンプレート化されたヘルパー関数(それ自体は仮想的ではない)を使用できます。

struct ObjectVisitorGetTypeInfo {
    Object::TypeInformation result;
    virtual void visit(const SomeObject& o) { doVisit(o); }
    virtual void visit(const AnotherObject& o) { doVisit(o); }
    // Again, an implementation for each concrete Object.

  private:
    template <typename T>
    void doVisit(const T& o) {
        result = T::GetTypeInformation();
    }
};
0
Ben

それは不可能ですが、それは単に省略のためです。多くの人々が主張しているように、それは「意味をなさない」ものではありません。明確にするために、私はこのようなことについて話している:

struct Base {
  static virtual void sayMyName() {
    cout << "Base\n";
  }
};

struct Derived : public Base {
  static void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
  Derived::sayMyName(); // Also would work.
}

これはcouldが実装されている(実装されていない)100%の何かであり、有用であると主張します。

通常の仮想機能がどのように機能するかを検討してください。 staticsを削除して、他のものを追加すると、次のようになります。

struct Base {
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  void sayMyName() override {
    cout << "Derived\n";
  }
};

void foo(Base *b) {
  b->sayMyName();
}

これは正常に機能し、基本的にコンパイラはVTablesと呼ばれる2つのテーブルを作成し、このような仮想関数にインデックスを割り当てます

enum Base_Virtual_Functions {
  sayMyName = 0;
  foo = 1;
};

using VTable = void*[];

const VTable Base_VTable = {
  &Base::sayMyName,
  &Base::foo
};

const VTable Derived_VTable = {
  &Derived::sayMyName,
  &Base::foo
};

次に、仮想関数を持つ各クラスには、VTableを指す別のフィールドが追加されているため、コンパイラは基本的に次のように変更します。

struct Base {
  VTable* vtable;
  virtual void sayMyName() {
    cout << "Base\n";
  }
  virtual void foo() {
  }
  int somedata;
};

struct Derived : public Base {
  VTable* vtable;
  void sayMyName() override {
    cout << "Derived\n";
  }
};

それでは、b->sayMyName()を呼び出したときに実際に何が起こりますか?基本的にこれ:

b->vtable[Base_Virtual_Functions::sayMyName](b);

(最初のパラメーターはthisになります。)

OKさて、静的メンバー関数と非静的メンバー関数の違いは何ですか?唯一の違いは、後者がthisポインターを取得することです。

静的仮想関数でもまったく同じことができます-thisポインターを削除するだけです。

b->vtable[Base_Virtual_Functions::sayMyName]();

これにより、両方の構文をサポートできます。

b->sayMyName(); // Prints "Base" or "Derived"...
Base::sayMyName(); // Always prints "Base".

したがって、すべての否定論者を無視してください。それdoes理にかなっています。なぜサポートされないのですか?メリットがほとんどなく、少し混乱することさえあるからだと思います。

通常の仮想関数に対する唯一の技術的な利点は、関数にthisを渡す必要がないことですが、パフォーマンスに測定可能な差が生じるとは思いません。

インスタンスがある場合、およびインスタンスがない場合に個別の静的関数と非静的関数がないことを意味しますが、使用するときに実際に「仮想」のみであると混乱する可能性がありますインスタンス呼び出し。

0
Timmmm

C++では、crtメソッドで静的継承を使用できます。この例では、ウィンドウテンプレートatl&wtlで広く使用されています。

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern を参照してください

簡単にするために、クラスmyclass:public myancestorのようにそれ自体からテンプレート化されたクラスがあります。この時点から、myancestorクラスは静的なT :: YourImpl関数を呼び出すことができます。

0
user10628663