代码之家  ›  专栏  ›  技术社区  ›  Baiyan Huang

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

  •  16
  • Baiyan Huang  · 技术社区  · 16 年前

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

    注:

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

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

    class Base{};
    class Derived: public Base{};
    
    void Func(Base)
    {
    
    }
    
    //void Func(Derived)
    //{
    //
    //}
    
    //main
    Func(Derived());
    

    在这里,如果我对第二个函数进行注释,那么将调用第一个函数——编译器(包括VC++和GCC)对此感到满意。

    是C++标准吗?当遇到这样的代码时,我能让编译器(VC++)给我一个警告吗?

    非常感谢!!!!

    编辑:

    非常感谢你的帮助!

    我找不到一个编译器选项来给出错误/警告-我甚至在针对VC++编译器顾问的msdn论坛上发布了这个选项,没有答案。所以,恐怕gcc和vc++都没有实现这个特性。

    因此,添加以派生类为参数的构造函数是目前最好的解决方案。

    编辑

    我已经向微软提交了一份Feedbak,希望他们能尽快解决:

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

    -白岩

    8 回复  |  直到 9 年前
        1
  •  15
  •   Andrew Khosravian    16 年前

    如果可以修改基类,可以执行以下操作:

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

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

        2
  •  8
  •   Community CDub    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 */ }
    };
    

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

    对于世界上的编译器实现者: 测试对象切片绝对是值得为其创建(可选)警告的!我想不出一个例子,它是期望的行为,它在新版C++代码中是很常见的。

    [编辑日期:2015年3月27日:] 正如MattMcNab所指出的,实际上您不需要像上面所做的那样显式地声明复制构造函数和赋值运算符,因为编译器仍然隐式地声明它们。在2003 C++标准中,这在脚注106中明确地提到了在128/2:

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

        3
  •  4
  •   Nick    16 年前

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

    因此,在您的示例中,代码如下所示(对于Visual Studio):

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

    这在我的测试中有效(编译时警告)。注意,它并不能解决复制问题,但是一个构造类似的赋值操作符应该在这里完成这个技巧。

    希望这有帮助。:)

        4
  •  1
  •   Greg Hewgill    16 年前

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

    我相信我已经使用了一个编译器,它有一个警告,您可以通过它来检测和警告这个问题。但是,我不记得是哪一个。

        5
  •  1
  •   Drew Hall    16 年前

    不是解决你眼前问题的办法,而是……

    大多数将类/结构对象作为参数的函数都应将参数声明为“const x&或”x&“类型,除非它们有非常好的理由不这样做。

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

        6
  •  1
  •   CB Bailey    16 年前

    解决这个问题的最佳方法通常是遵循斯科特·迈耶的建议(参见 有效C++ )只在继承树的叶节点上有具体的类,并且通过至少一个纯虚拟函数(如果没有其他函数,则是析构函数)确保非叶类是抽象的。

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

    编辑

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

    我的理由是。

    考虑一些库代码,它定义了一个类concrete1,并在这个函数的推理中使用它。

    void do_something( const Concrete1& c );
    

    传递类型be引用是为了提高效率,通常是一个好主意。如果库将concrete1视为值类型,则实现可能会决定复制输入参数。

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

    如果传递的引用的对象类型确实是, Concrete1 而不是其他派生类型,那么这个代码就可以了,不执行切片。关于这个的一般警告 push_back 函数调用可能只会产生误报,而且很可能是无效的。

    考虑一些派生的客户端代码 Concrete2 混凝土1 并将其传递给另一个函数。

    void do_something_else( const Concrete1& c );
    

    因为参数是引用获取的,所以在要检查的参数上此处不会发生切片,因此在此处警告切片是不正确的,因为可能不会发生切片。将派生类型传递给接受引用或指针的函数是利用多态类型的一种常见而有用的方法,因此警告或不允许这样做可能会适得其反。

    那么哪里有错误呢?好吧,“错误”是传递一个对某个东西的引用,这个东西是从一个类派生出来的,然后被调用的函数将这个类视为一个值类型。

    一般来说,没有办法生成一个针对对象切片的持续有用的编译时警告,这就是为什么在可能的情况下,最好的防御是通过设计消除问题。

        7
  •  1
  •   Motti    16 年前
    class Derived: public Base{};
    

    你是说派生是一个基,所以它应该在任何取基的函数中工作。如果这是一个真正的问题,那么继承可能不是你真正想要使用的。

        8
  •  0
  •   hichris123 Andreas Sewe    9 年前

    我稍微修改了你的代码:

    class Base{
      public:
        Base() {}
        explicit Base(const Base &) {}
    };
    
    class Derived: public Base {};
    
    void Func(Base)
    {
    
    }
    
    //void Func(Derived)
    //{
    //
    //}
    
    //main
    int main() {
      Func(Derived());
    }
    

    显式关键字将确保构造函数不会用作隐式转换运算符-您必须在要使用它时显式调用它。