クラスメンバー関数テンプレートを仮想化できますか?


304

C ++クラスメンバー関数テンプレートは仮想化できないと聞いたことがあります。これは本当ですか?

それらが仮想である場合、そのような機能を使用するシナリオの例は何ですか?


12
私も同様の問題に直面し、同時に仮想化とテンプレート化を行うことには議論の余地があることも学びました。私の解決策は、派生クラスに共通のテンプレートマジックを記述し、特殊な部分を実行する純粋な仮想関数を呼び出すことでした。これはもちろん私の問題の性質に関連しているので、すべての場合に機能するとは限りません。
タマシュSzelei

回答:


329

テンプレートは、コンパイル時にコードを生成するコンパイラに関するものです。仮想関数はすべて、実行時に呼び出す関数を判別するランタイムシステムに関するものです。

ランタイムシステムがテンプレート化された仮想関数を呼び出す必要があると判断すると、コンパイルはすべて完了し、コンパイラーは適切なインスタンスを生成できなくなります。したがって、仮想メンバー関数テンプレートを持つことはできません。

ただし、ポリモーフィズムとテンプレートの組み合わせから生じるいくつかの強力で興味深い手法、特にいわゆる型消去があります。


32
言語の理由はわかりません。実装の理由だけです。vtablesは言語の一部ではなく、コンパイラが言語を実装する標準的な方法にすぎません。
gerardw 2013年

16
Virtual functions are all about the run-time system figuring out which function to call at run-time-申し訳ありませんが、これはかなり間違った方法であり、かなり混乱しています。これは単なる間接的なものであり、「実行時の計算」は含まれません。コンパイル時に、呼び出される関数はvtableのn番目のポインターが指すものであることがわかっています。「考え出す」とは、型チェックなどがあることを意味しますが、そうではありません。Once the run-time system figured out it would need to call a templatized virtual function-関数が仮想かどうかはコンパイル時にわかります。
dtech 2015

9
@ddriver:1.コンパイラがを見つけた場合、void f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }呼び出された時点で呼び出された関数を「認識」しており、のcb.f()ことを知りませんvb.f()。後者は、実行時システムによって、実行に検出される必要があります。これを「計算」と呼びたいかどうか、またこれが多かれ少なかれ効率的であるかどうかにかかわらず、これらの事実は少しも変わりません。
sbi

9
@ddriver:2.(メンバー)関数テンプレートのインスタンスは(メンバー)関数であるため、そのようなインスタンスへのポインターをvtableに置いても何の問題もありません。ただし、どのテンプレートインスタンスが必要かは、呼び出し元がコンパイルされたときにのみわかりますが、vtableは、基本クラスと派生クラスがコンパイルされたときに設定されます。そして、これらはすべて個別にコンパイルされます。さらに悪いことに、実行時に新しい派生クラスを実行中のシステムにリンクできます(ブラウザーがプラグインを動的にロードしていると考えてください)。新しい派生クラスが作成されると、呼び出し元のソースコードでさえ長い間失われる可能性があります。
sbi

9
@sbi:なぜ私の名前に基づいて仮定をしているのですか?ジェネリックとテンプレートを混同しませんでした。Javaのジェネリックスは純粋にランタイムであることを知っています。C ++で仮想メンバー関数テンプレートを作成できない理由を徹底的に説明していませんでしたが、InQsitiveでは説明しました。テンプレートと仮想メカニズムを過度に簡略化して「コンパイル時」と「実行時」を比較し、「仮想メンバー関数テンプレートを作成することはできない」と結論付けました。「C ++テンプレートの完全ガイド」を参照するInQsitiveの回答を参照しました。それは「手を振る」とは思わない。ごきげんよう。
Javanator 2016

133

C ++テンプレートから完全ガイド:

メンバー関数テンプレートは仮想として宣言できません。仮想関数呼び出しメカニズムの通常の実装では、仮想関数ごとに1つのエントリを持つ固定サイズのテーブルを使用するため、この制約が課されます。ただし、メンバー関数テンプレートのインスタンス化の数は、プログラム全体が翻訳されるまで固定されません。したがって、仮想メンバー関数テンプレートをサポートするには、C ++コンパイラとリンカーでまったく新しい種類のメカニズムをサポートする必要があります。対照的に、クラステンプレートの通常のメンバーは、クラスがインスタンス化されるときにその数が固定されるため、仮想にすることができます


8
特にリンク時最適化サポートを備えた今日のC ++コンパイラーとリンカーは、リンク時に必要なvtableとオフセットを生成できるはずです。では、C ++ 2bでこの機能を取得するのでしょうか?
Kai Petzke

33

現在、C ++は仮想テンプレートメンバー関数を許可していません。最も可能性の高い理由は、実装の複雑さです。Rajendraは、現時点でそれができない理由を説明していますが、標準を合理的に変更することで可能になる可能性があります。特に、仮想関数呼び出しの場所を考慮すると、テンプレート関数のインスタンス化が実際にいくつ存在するかを調べ、vtableを構築することは難しいようです。標準の人々は今やるべきことが他にもたくさんあるだけであり、C ++ 1xはコンパイラー作成者にとっても多くの作業です。

テンプレート化されたメンバー関数が必要になるのはいつですか?私はかつて、純粋な仮想基本クラスで階層をリファクタリングしようとする状況に遭遇しました。これは、さまざまな戦略を実装するための貧弱なスタイルでした。仮想関数の1つの引数を数値型に変更し、メンバー関数をオーバーロードする代わりに、仮想テンプレート関数を使用しようとしたすべてのサブクラスのすべてのオーバーロードをオーバーライドしたかった(そして、それらが存在しないことを確認する必要があった) 。)


5
@pmr:関数のコンパイル時に存在していなかったコードから仮想関数が呼び出される可能性があります。コンパイラは、存在しないコードに対して生成する(理論上の)仮想テンプレートメンバー関数のインスタンスをどのように決定しますか?
sbi

2
@sbi:はい、個別のコンパイルは大きな問題になります。私はC ++コンパイラの専門家ではないので、ソリューションを提供できません。テンプレート化された関数と同様に、すべてのコンパイル単位で再びインスタンス化する必要がありますよね?それで問題は解決しませんか?
pmr 2010年

2
ライブラリを動的にロードすることを指している場合、@ sbiは、仮想テンプレートメソッドだけでなく、テンプレートクラス/関数の一般的な問題です。
オークランド

「C ++は[...]を許可しません」 -標準への参照を参照していただければ幸いです(回答が書かれたときの最新のものか、8年後の最新のものかに関わらず)...
アコンカグア

19

仮想関数テーブル

仮想関数テーブルの背景とその機能(ソース)から始めましょう。

[20.3]仮想メンバー関数と非仮想メンバー関数の呼び出し方法の違いは何ですか?

非仮想メンバー関数は静的に解決されます。つまり、メンバー関数は、オブジェクトへのポインター(または参照)の型に基づいて静的に(コンパイル時に)選択されます。

対照的に、仮想メンバー関数は動的に(実行時に)解決されます。つまり、メンバー関数は、そのオブジェクトへのポインター/参照のタイプではなく、オブジェクトのタイプに基づいて(実行時に)動的に選択されます。これは「動的バインディング」と呼ばれます。ほとんどのコンパイラーは、次の手法のバリアントを使用します。オブジェクトに1つ以上の仮想関数がある場合、コンパイラーは「仮想ポインター」または「vポインター」と呼ばれる隠しポインターをオブジェクトに配置します。このvポインターは、「仮想テーブル」または「vテーブル」と呼ばれるグローバルテーブルを指します。

コンパイラーは、少なくとも1つの仮想関数を持つクラスごとにvテーブルを作成します。たとえば、クラスCircleがdraw()およびmove()とresize()の仮想関数を持っている場合、gazillion Circleオブジェクトがあり、vポインタがこれらのCircleオブジェクトはそれぞれ、Circle vテーブルをポイントします。vテーブル自体には、クラス内の各仮想関数へのポインターがあります。たとえば、Circle vテーブルには3つのポインタがあります。Circle:: draw()へのポインタ、Circle :: move()へのポインタ、Circle :: resize()へのポインタです。

仮想関数のディスパッチ中、ランタイムシステムはオブジェクトのvポインターをクラスのvテーブルに移動し、次にvテーブルの適切なスロットをメソッドコードに移動します。

上記の手法のスペースコストのオーバーヘッドはわずかです。オブジェクトごとに追加のポインター(ただし、動的バインディングを実行する必要があるオブジェクトのみ)と、メソッドごとに追加のポインター(仮想メソッドのみ)があります。時間コストのオーバーヘッドもかなりわずかです。通常の関数呼び出しと比較して、仮想関数呼び出しには2つの追加のフェッチが必要です(1つはvポインターの値を取得するため、もう1つはメソッドのアドレスを取得するため)。コンパイラーはポインターのタイプに基づいてコンパイル時に排他的に非仮想関数を解決するため、このランタイム活動は非仮想関数では発生しません。


私の問題、または私がここに来た方法

私はこのようなものを、さまざまなタイプのキューブ(一部はピクセルで保存され、一部は画像で保存されるなど)に異なる方法で実装されるテンプレート化された最適化されたロード関数を含むcubefile基本クラスに使用しようとしています。

いくつかのコード:

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

私はそれを望んでいますが、仮想テンプレート化されたコンボのためにコンパイルされません:

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

テンプレート宣言をクラスレベルに移動してしまいました。このソリューションでは、プログラムがそれらを読み取る前に読み取る特定のタイプのデータについて知る必要がありましたが、これは受け入れられません。

解決

警告、これはあまりきれいではありませんが、反復実行コードを削除することができました

1)基本クラス

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2)と子クラス

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

LoadAnyCubeは基本クラスで宣言されていないことに注意してください。


回避策を含む別のスタックオーバーフローの回答を次に示します 。仮想テンプレートメンバーの回避策が必要です。


1
私は同じ状況に出会い、集団階級の継承構造に出会いました。マクロが役立ちました。
ZFY

16

次のコードは、Windows 7でMinGW G ++ 3.4.5を使用して、コンパイルして適切に実行できます。

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

そして出力は:

A:A<string> a
A<--B:B<string> c
A<--B:3

その後、新しいクラスXを追加しました。

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

次のようにmain()でクラスXを使用しようとしたとき:

X x;
x.func2<string>("X x");

g ++は次のエラーを報告します。

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

したがって、次のことが明らかです。

  • 仮想メンバー関数は、クラステンプレートで使用できます。コンパイラがvtableを構築するのは簡単です
  • ご覧のように、クラステンプレートメンバー関数を仮想として定義することは不可能です。関数のシグネチャを特定してvtableエントリを割り当てることは困難です。

19
クラステンプレートには、仮想メンバー関数を含めることができます。メンバー関数は、メンバー関数テンプレートと仮想メンバー関数の両方になることはできません。
James McNellis、

1
実際にはgcc 4.4.3で失敗します。私のシステムでは確かにUbuntu 10.04
blueskin

3
これは、質問の内容とはまったく異なります。ここでは、基本クラス全体がテンプレート化されています。このようなことは以前にまとめたことがあります。これはVisual Studio 2010でもコンパイルできます
ds-bos-msk

14

いいえ、できません。だが:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

共通のインターフェースを持ち、サブクラスへの実装を延期するだけの場合も、ほとんど同じ効果があります。


3
好奇心が強い場合、これはCRTPと呼ばれます。
マイケル・チョイ

1
しかし、これは、クラス階層があり、基本クラスへのポインタの仮想メソッドを呼び出せるようにしたい場合には役立ちません。あなたのFooようポインタは、資格のあるFoo<Bar>、それが指すことができない、Foo<Barf>またはFoo<XXX>
Kai Petzke

@KaiPetzke:制約のないポインターを作成することはできません。しかし、具象型を知る必要のない任意のコードをテンプレート化できます。これは、ほとんど同じ効果があります(概念的には少なくとも-明らかに完全に異なる実装です)。
トム

8

いいえ、テンプレートメンバー関数は仮想化できません。


9
私の好奇心は:なぜですか?そうすることでコンパイラはどのような問題に直面しますか?
WannaBeGeek 2010年

1
スコープ内で宣言が必要です(少なくとも、型を正しくするために)。標準(および言語)では、使用する識別子のスコープ内で宣言を行う必要があります。
dirkgently

4

他の回答では、提案されたテンプレート関数はファサードであり、実際的なメリットはありません。

  • テンプレート関数は、さまざまなタイプを使用して1回だけコードを記述する場合に役立ちます。
  • 仮想関数は、さまざまなクラスに共通のインターフェースを持たせるのに役立ちます。

言語は仮想テンプレート関数を許可していませんが、回避策を使用すると、両方の機能を持つことができます。たとえば、クラスごとに1つのテンプレート実装と仮想共通インターフェースがあります。

ただし、テンプレートタイプの組み合わせごとに、ダミーの仮想ラッパー関数を定義する必要があります。

#include <memory>
#include <iostream>
#include <iomanip>

//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
    virtual void getArea(float &area) = 0;
    virtual void getArea(long double &area) = 0;
};

//---------------------------------------------
// Square
class Square : public Geometry {
public:
    float size {1};

    // virtual wrapper functions call template function for square
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for squares
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(size * size);
    }
};

//---------------------------------------------
// Circle
class Circle : public Geometry  {
public:
    float radius {1};

    // virtual wrapper functions call template function for circle
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for Circles
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(radius * radius * 3.1415926535897932385L);
    }
};


//---------------------------------------------
// Main
int main()
{
    // get area of square using template based function T=float
    std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
    float areaSquare;
    geometry->getArea(areaSquare);

    // get area of circle using template based function T=long double
    geometry = std::make_unique<Circle>();
    long double areaCircle;
    geometry->getArea(areaCircle);

    std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
    return 0;
}

出力:

正方形の面積は1、円の面積は3.1415926535897932385

ここで試してください


3

質問の2番目の部分に答えるには:

それらが仮想である場合、そのような機能を使用するシナリオの例は何ですか?

これは無理なことではありません。たとえば、Java(すべてのメソッドが仮想である)は、ジェネリックメソッドに問題はありません。

C ++で仮想関数テンプレートが必要な例の1つは、ジェネリックイテレータを受け入れるメンバー関数です。または、ジェネリック関数オブジェクトを受け入れるメンバー関数。

この問題の解決策は、型消去をboost :: any_rangeおよびboost :: functionで使用することです。これにより、関数をテンプレートにすることなく、汎用のイテレーターまたはファンクターを受け入れることができます。


6
Javaジェネリックは、キャスト用の構文糖衣です。テンプレートとは異なります。
Brice M. Dempsey 2014年

2
@ BriceM.Dempsey:キャスティングはJavaがジェネリックを実装する方法であり、逆ではありません...そして意味論的には、ユースケースのexclipyが有効なIMOです。
einpoklum 2016年

2

テンプレートメソッドのタイプのセットが事前にわかっている場合は、「仮想テンプレートメソッド」の回避策があります。

アイデアを示すために、以下の例では2つのタイプのみが使用されています(intおよびdouble)。

そこで、「仮想」テンプレートメソッド(Base::Method)が対応する仮想メソッド(の1つBase::VMethod)を呼び出し、次にそれがテンプレートメソッド実装(Impl::TMethod)を呼び出します。

TMethod派生実装(AImplBImpl)でテンプレートメソッドを実装して使用するだけで済みますDerived<*Impl>

class Base
{
public:
    virtual ~Base()
    {
    }

    template <typename T>
    T Method(T t)
    {
        return VMethod(t);
    }

private:
    virtual int VMethod(int t) = 0;
    virtual double VMethod(double t) = 0;
};

template <class Impl>
class Derived : public Impl
{
public:
    template <class... TArgs>
    Derived(TArgs&&... args)
        : Impl(std::forward<TArgs>(args)...)
    {
    }

private:
    int VMethod(int t) final
    {
        return Impl::TMethod(t);
    }

    double VMethod(double t) final
    {
        return Impl::TMethod(t);
    }
};

class AImpl : public Base
{
protected:
    AImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t - i;
    }

private:
    int i;
};

using A = Derived<AImpl>;

class BImpl : public Base
{
protected:
    BImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t + i;
    }

private:
    int i;
};

using B = Derived<BImpl>;

int main(int argc, const char* argv[])
{
    A a(1);
    B b(1);
    Base* base = nullptr;

    base = &a;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;

    base = &b;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;
}

出力:

0
1
2
3

注意: Base::Method実際のコードでは余剰です(VMethod公開して直接使用できます)。実際の「仮想」テンプレートメソッドのように見えるように追加しました。


私は仕事で問題を解決している間にこの解決策を思いつきました。マークエッセルのものと似ているようですが、実装と説明が改善されていると思います。
sad1raf 2017年

私はすでにこれをコード難読化と見なしていますが、Baseこれまでに実装されたものと互換性のない引数型を使用してテンプレート関数を呼び出す必要があるたびに元のクラスを変更する必要があるという事実を回避することはできません。テンプレートの目的は、この必要性を回避することです...
アコンカグア

Esselsのアプローチは完全に異なります。通常の仮想関数は異なるテンプレートのインスタンス化を受け入れます-派生クラスの最終的なテンプレート関数はコードの重複を回避するためにのみ役立ち、基本クラスに対応するものさえありません...
Aconcagua

2

多くの人が答えてきた古い質問は、投稿された他の方法とそれほど変わらない簡潔な方法であると思いますが、マイナーマクロを使用してクラス宣言の複製を容易にすることです。

// abstract.h

// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
    void render(int a, char *b) override { render_internal<char>(a, b); }   \
    void render(int a, short *b) override { render_internal<short>(a, b); } \
    // ...

class Renderable
{
public:
    // Then, once for each on the abstract
    virtual void render(int a, char *a) = 0;
    virtual void render(int a, short *b) = 0;
    // ...
};

さて、サブクラスを実装するには:

class Box : public Renderable
{
public:
    IMPL_RENDER() // Builds the functions we want

private:
    template<typename T>
    void render_internal(int a, T *b); // One spot for our logic
};

ここでの利点は、新しくサポートされたタイプを追加するときに、すべて抽象ヘッダーから実行でき、複数のソース/ヘッダーファイルで修正することを控えることができるということです。


0

少なくともgcc 5.4では、仮想関数はテンプレートメンバーになることができますが、それ自体がテンプレートでなければなりません。

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

アウトプット

mix before a2
Process finished with exit code 0

0

これを試して:

classeder.hに書き込みます。

template <typename T>
class Example{
public:
    T c_value;

    Example(){}

    T Set(T variable)
    {
          return variable;
    }

    virtual Example VirtualFunc(Example paraM)
    {
         return paraM.Set(c_value);
    }

これを使用している場合は、このコードをmain.cppに書き込むことを確認してください。

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.