如何在对象切片时生成编译器警告/错误

时间:2009-02-24 03:37:10

标签: c++ compiler-construction truncate slice

我想知道是否可以让编译器为代码发出警告/错误,如下所示:

注意:

1。是的,它是糟糕的编程风格,我们应该避免这种情况 - 但我们正在处理遗留代码,并希望编译器可以帮助我们识别这些情况。)

2。我更喜欢编译器选项(VC ++)来禁用或启用对象切片(如果有的话)。

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

void Func(Base)
{

}

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

//main
Func(Derived());

如果我注释掉第二个函数,那么第一个函数将被调用 - 编译器(VC ++和Gcc)对此感觉很舒服。

是C ++标准吗?我可以请求编译器(VC ++)在遇到这样的代码时给我一个警告吗?

非常感谢!!!

修改

非常感谢你的帮助!

我找不到编译器选项来发出错误/警告 - 我甚至在MSDN论坛上发布了这个VC ++编译器顾问,没有回答。所以我担心gcc和vc ++都没有实现这个功能。

因此,添加构造函数将派生类作为参数将是现在最好的解决方案。

修改

我已经向MS提交了一个feedbak,希望他们能尽快修复它:

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

-Baiyan

8 个答案:

答案 0 :(得分:15)

如果你可以修改基类,你可以这样做:

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

取决于您的编译器应该为您提供翻译单元,也可能是切片发生的功能。

答案 1 :(得分:8)

作为Andrew Khosravian's answer的变体,我建议使用模板化复制构造函数和赋值运算符。这样,您不需要知道给定基类的所有派生类,以保护该基类不被切片:

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 */ }
};

虽然这减少了所需的工作量,但是通过这种方法,您仍然需要弄乱每个基类以保护它。此外,如果BaseSomeOtherClass的合法转化使用了operator Base()成员SomeOtherClass,则会导致问题。 (在这种情况下,可以使用涉及boost::disable_if<is_same<T, SomeOtherClass> >的更精细的解决方案。)在任何情况下,一旦确定了对象切片的所有实例,就应该删除此代码。

对于世界的编译器实现者:测试对象切片绝对值得创建(可选)警告!我无法想到一个需要行为的实例,它在新手C ++代码中很常见。

[编辑27/3/2015:] 正如Matt McNab所指出的那样,你实际上不需要像我上面那样明确地声明复制构造函数和赋值运算符,因为它们仍将由编译器隐式声明。在2003 C ++标准中,这在12.8 / 2下的脚注106中明确提到:

  

因为模板构造函数永远不是复制构造函数,所以这种模板的存在不会抑制复制构造函数的隐式声明。模板构造函数与其他构造函数(包括复制构造函数)一起参与重载解析,如果模板构造函数提供了比其他构造函数更好的匹配,则模板构造函数可用于复制对象。

答案 2 :(得分:4)

我建议在你的基类中添加一个构造函数,它明确地对派生类进行const引用(使用前向声明)。在我的简单测试应用程序中,在切片情况下调用此构造函数。然后,您至少可以获得运行时断言,并且您可以通过巧妙地使用模板来获得编译时断言(例如:以在该构造函数中生成编译时断言的方式实例化模板)。调用显式函数时,可能还有编译器特定的方法来获取编译时警告或错误;例如,您可以使用“__declspec(不建议使用)”作为Visual Studio中的“切片构造函数”来获取编译时警告,至少在函数调用的情况下。

因此在您的示例中,代码看起来像这样(对于Visual Studio):

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

这适用于我的测试(编译时警告)。请注意,它不能解决复制情况,但是类似构造的赋值运算符应该可以解决这个问题。

希望这会有所帮助。 :)

答案 3 :(得分:1)

这通常被称为Object Slicing,这是一个众所周知的问题,有自己的维基百科文章(尽管它只是对问题的简短描述)。

我相信我使用的编译器有一个警告,你可以启用它来检测和警告这个。但是,我不记得是哪一个。

答案 4 :(得分:1)

不是解决您当前问题的方法,但......

将class / struct对象作为参数的大多数函数都应该声明参数类型为“const X&amp;”或“X&amp;”,除非他们有充分理由不这样做。

如果你总是这样做,对象切片永远不会成为问题(引用不会被切片!)。

答案 5 :(得分:1)

class Derived: public Base{};

你说Derived是一个基础,因此它应该适用于任何需要基础的函数。如果这是一个真正的问题,那么继承可能不是你真正想要使用的。

答案 6 :(得分:1)

解决此问题的最佳方法通常是遵循Scott Meyer的建议(参见 Effective C ++ ),只建议在继承树的叶节点上使用具体类,并确保非叶类是通过至少有一个纯虚函数来抽象(析构函数,如果没有别的话)。

令人惊讶的是,这种方法有时会以其他方式帮助澄清设计。在任何情况下,隔离通用抽象接口的努力通常都是值得的设计工作。

修改

虽然我最初没有说清楚,但我的答案来自于这样一个事实:在编译时不可能准确地警告对象切片,因此如果你有一个错误的安全感,它会导致错误的安全感。编译时断言,或启用编译器警告。如果您需要了解对象切片的实例并需要更正它们,那么它意味着您有改变遗留代码的愿望和能力。如果是这种情况,那么我认为你应该认真考虑重构类层次结构,以此来使代码更加健壮。

我的理由是这样。

考虑一些定义了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 );

因为参数是通过引用获取的,所以在此处不会对要检查的参数进行切片,因此在此处警告切片是不正确的,因为可能不会发生切片。将派生类型传递给带引用或指针的函数是利用多态类型的常用且有用的方法,因此警告或禁止此类似乎会适得其反。

那么哪里有错误?那么'错误'就是传递一个对类的派生的引用,然后将它视为被调用函数的值类型。

通常,没有办法为对象切片生成一致有用的编译时警告,这就是为什么尽可能最好的防御是通过设计消除问题。

答案 7 :(得分: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关键字将确保构造函数不会被用作隐式转换运算符 - 当您想要使用它时必须显式调用它。