18

次のように、コンパイラにコードの警告/エラーを発行させることが可能かどうかを知りたいです。

ノート:

1. ええ、それは悪いプログラミング スタイルであり、そのようなケースは避けるべきです - しかし、私たちはレガシ コードを扱っており、コンパイラがそのようなケースを特定するのに役立つことを願っています.)

2. オブジェクトのスライスを無効または有効にするコンパイラ オプション (VC++) がある場合は、それを使用したいと思います。

class Base{};
class Derived: public Base{};

void Func(Base)
{

}

//void Func(Derived)
//{
//
//}

//main
Func(Derived());

ここで、2 番目の関数をコメント アウトすると、最初の関数が呼び出され、コンパイラ (VC++ と Gcc の両方) はそれを快適に感じます。

C++標準ですか?そのようなコードに遭遇したときに警告を出すようにコンパイラ (VC++) に依頼できますか?

本当にありがとう!!!

編集:

助けてくれて本当にありがとう!

エラー/警告を表示するコンパイラ オプションが見つかりません。これを VC++ コンパイラ コンサルタントの MSDN フォーラムに回答なしで投稿しました。したがって、残念ながら gcc も vc++ もこの機能を実装していません。

そのため、派生クラスをパラメーターとして受け取るコンストラクターを追加することが、今のところ最善の解決策です。

編集

MS にフィードバックを送信しました。すぐに修正されることを願っています。

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=421579

-バイヤン

4

8 に答える 8

16

基本クラスを変更できる場合は、次のようにすることができます。

class Base
{
public:
// not implemented will cause a link error
    Base(const Derived &d);
    const Base &operator=(const Derived &rhs);
};

翻訳単位を取得する必要があるコンパイラ、およびおそらくスライスが行われている関数によって異なります。

于 2009-02-24T05:09:03.167 に答える
9

Andrew Khosravian の回答のバリエーションとして、テンプレート化されたコピー コンストラクターと代入演算子を使用することをお勧めします。このようにして、特定の基本クラスをスライスから保護するために、その基本クラスのすべての派生クラスを知る必要はありません。

class Base
{
private:   // To force a compile error for non-friends (thanks bk1e)
// Not implemented, so will cause a link error for friends
    template<typename T> Base(T const& d);
    template<typename T> Base const& operator=(T const& rhs);

public:
// You now need to provide a copy ctor and assignment operator for Base
    Base(Base const& d) { /* Initialise *this from d */ }
    Base const& operator=(Base const& rhs) { /* Copy d to *this */ }
};

これにより必要な作業量は減りますが、このアプローチでは、保護するために各基本クラスをいじる必要があります。また、 のメンバーを使用するからBaseへの正当な変換がある場合、問題が発生します。(その場合は、より精巧なソリューションを使用できます。) いずれにせよ、オブジェクト スライスのすべてのインスタンスを特定したら、このコードを削除する必要があります。SomeOtherClassoperator Base()SomeOtherClassboost::disable_if<is_same<T, SomeOtherClass> >

世界中のコンパイラ実装者へ:オブジェクトのスライスのテストは、(オプションの) 警告を作成する価値のあるものです! 望ましい動作となるインスタンスは 1 つも思いつきません。これは、初心者の C++ コードでよく見られます。

[2015 年 3 月 27 日編集:] Matt McNab が指摘したように、コピー コンストラクターと代入演算子は、コンパイラによって暗黙的に宣言されるため、上記のように明示的に宣言する必要はありません。2003 C++ 標準では、これは 12.8/2 の脚注 106 で明示的に言及されています。

テンプレート コンストラクターは決してコピー コンストラクターではないため、そのようなテンプレートが存在しても、コピー コンストラクターの暗黙的な宣言が抑制されることはありません。テンプレート コンストラクターは、コピー コンストラクターを含む他のコンストラクターとのオーバーロードの解決に参加します。テンプレート コンストラクターは、他のコンストラクターよりも適切に一致する場合、オブジェクトをコピーするために使用できます。

于 2009-02-24T11:20:18.190 に答える
4

派生クラスへの const 参照を明示的に (前方宣言で) 取るコンストラクターを基本クラスに追加することをお勧めします。私の単純なテスト アプリでは、このコンストラクターはスライス ケースで呼び出されます。その後、少なくとも実行時のアサーションを取得できます。また、テンプレートを巧みに使用してコンパイル時のアサーションを取得することもできます (たとえば、そのコンストラクターでコンパイル時のアサーションを生成する方法でテンプレートをインスタンス化します)。明示的な関数を呼び出すときに、コンパイル時の警告またはエラーを取得するコンパイラ固有の方法もある場合があります。たとえば、Visual Studio の「スライス コンストラクター」に「__declspec(deprecated)」を使用して、少なくとも関数呼び出しの場合にコンパイル時の警告を取得できます。

したがって、あなたの例では、コードは次のようになります(Visual Studioの場合):

class Base { ...
    __declspec(deprecated) Base( const Derived& oOther )
    {
        // Static assert here if possible...
    }
...

これは私のテストで機能します(コンパイル時の警告)。これはコピー ケースを解決しないことに注意してください。

お役に立てれば。:)

于 2009-02-24T04:52:13.580 に答える
2

この問題に対処する最善の方法は、通常、Scott Meyer の推奨 (「Effective C++ 」を参照) に従うことです。具体的なクラスは継承ツリーのリーフ ノードにのみ配置し、非リーフ クラスは少なくとも 1 つの純粋仮想関数 (他に何もなければ、デストラクタ)。

このアプローチが、他の方法でも設計を明確にするのに役立つことが多いのは驚くべきことです。共通の抽象インターフェイスを分離する作業は、通常、いずれの場合でも価値のある設計作業です。

編集

最初はこれを明確にしていませんでしたが、私の答えは、コンパイル時にオブジェクトのスライスについて正確に警告することは不可能であり、このため、コンパイル時のアサーションがある場合、誤った安心感につながる可能性があるという事実から来ています。 、またはコンパイラの警告が有効になっています。オブジェクト スライスのインスタンスを見つけて修正する必要がある場合は、レガシー コードを変更したいという願望と能力があることを意味します。この場合、コードをより堅牢にする方法として、クラス階層のリファクタリングを真剣に検討する必要があると思います。

私の理屈はこうです。

クラス Concrete1 を定義し、この関数へのインターフェイスでそれを使用するライブラリ コードを考えてみましょう。

void do_something( const Concrete1& c );

参照型を渡すのは効率のためであり、一般的には良い考えです。ライブラリが Concrete1 を値型と見なす場合、実装は入力パラメーターのコピーを作成することを決定する場合があります。

void do_something( const Concrete1& c )
{
    // ...
    some_storage.push_back( c );
    // ...
}

渡された参照のオブジェクト型が実際にConcrete1他の派生型ではない場合、このコードは問題なく、スライスは実行されません。この関数の呼び出しに関する一般的な警告は、push_back誤検知のみを生成する可能性があり、おそらく役に立たないでしょう。

Concrete2別の関数から派生しConcrete1、別の関数に渡すクライアント コードを考えてみましょう。

void do_something_else( const Concrete1& c );

パラメーターは参照によって取得されるため、ここではチェックするパラメーターでスライスが発生しません。したがって、スライスが発生しない可能性があるため、ここでスライスについて警告するのは正しくありません。参照またはポインターを受け取る関数に派生型を渡すことは、ポリモーフィック型を利用するための一般的で便利な方法であるため、これを警告したり許可しないことは逆効果に思えます。

では、どこにエラーがあるのでしょうか? 「間違い」は、呼び出された関数によって値型であるかのように扱われるクラスから派生したものへの参照を渡すことです。

一般に、オブジェクトのスライスに対して常に有用なコンパイル時警告を生成する方法はありません。これが、可能であれば、設計によって問題を排除することが最善の防御策である理由です。

于 2009-02-24T09:35:56.300 に答える
1
class Derived: public Base{};

Derived IS は Base であると言っているので、Base を使用するすべての関数で機能するはずです。これが実際の問題である場合、おそらく継承は本当に使用したいものではありません。

于 2009-02-24T04:01:38.290 に答える
1

これは一般にオブジェクト スライシングと呼ばれ、独自のウィキペディアの記事があるほどよく知られている問題です (ただし、問題の簡単な説明にすぎません)。

これを検出して警告するために有効にできる警告を持つコンパイラを使用したと思います。しかし、それがどれだったかは覚えていません。

于 2009-02-24T03:44:16.847 に答える
1

Not really a solution to your immediate problem, but....

Most functions that take class/struct objects as parameters should declare the parameters to be of type "const X&" or "X&", unless they have a very good reason not to.

If you always do this, object slicing will never be a problem (references don't get sliced!).

于 2009-02-24T03:54:09.883 に答える
0

私はあなたのコードを少し修正しました:

class Base{
  public:
    Base() {}
    explicit Base(const Base &) {}
};

class Derived: public Base {};

void Func(Base)
{

}

//void Func(Derived)
//{
//
//}

//main
int main() {
  Func(Derived());
}

explicit キーワードは、コンストラクターが暗黙的な変換演算子として使用されないようにします。使用する場合は、明示的に呼び出す必要があります。

于 2009-02-24T19:46:41.857 に答える