How to generate a compiler warning/error when object sliced

后端 未结 8 1562
执笔经年
执笔经年 2021-02-04 03:58

I want to know if it is possible to let compiler issue a warning/error for code as following:

Note:

1. Yea, it is bad programming style and

相关标签:
8条回答
  • 2021-02-04 04:02

    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!).

    0 讨论(0)
  • 2021-02-04 04:03

    If you can modify the base class you could do something like:

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

    Depending on your compiler that should get you the translation unit, and maybe the function where the slicing is happening.

    0 讨论(0)
  • 2021-02-04 04:03

    The best way to combat this problem is usually to follow Scott Meyer's recommendation (see Effective C++) of only having concrete classes at the leaf nodes of your inheritance tree and ensuring that non-leaf classes are abstract by having at least one pure virtual function (the destructor, if nothing else).

    It is surprising how often this approach helps clarify the design in other ways, as well. The effort of isolating a common abstract interface is usually a worthwhile design effort in any case.

    Edit

    Although I originally didn't make this clear, my answer comes from the fact that it is not possible to warn accurately about object slicing at compile time and for this reason it can lead to a false sense of security if you have a compile time assertion, or a compiler warning enabled. If you need to find out about instances of object slicing and need to correct them then it implies that you have the desire and ability to change the legacy code. If this is the case, then I believe that you should seriously consider refactoring the class hierarchy as a way of making the code more robust.

    My reasoning is this.

    Consider some library code that defines a class Concrete1 and uses it in the inferface to this function.

    void do_something( const Concrete1& c );
    

    Passing the type be reference is for efficiency and is, in general, a good idea. If the library considers Concrete1 to be a value type the implementation may decided to make a copy of the input parameter.

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

    If the object type of the passed reference is, indeed, Concrete1 and not some other derived type then this code is fine, no slicing is performed. A general warning on this push_back function invocation might produce only false positives and would most likely be unhelpful.

    Consider some client code that derives Concrete2 from Concrete1 and passes it into another function.

    void do_something_else( const Concrete1& c );
    

    Because the parameter is taken by reference no slicing occurs here on the parameter to check, so it would not be correct to warn here of slicing as it may be that no slicing occurs. Passing in a derived type to a function that takes a reference or pointer is a common and useful way to take advantage of polymorphic types so warning or disallowing this would seem counter-productive.

    So where is there error? Well the 'mistake' is passing in a reference to something that is derived from a class that is then treated as though it is a value type by the called function.

    There is, in general, no way to generate a consistently useful compile time warning against object slicing and this is why the best defence, where possible, is to eliminate the problem by design.

    0 讨论(0)
  • 2021-02-04 04:04

    I would suggest adding a constructor to your base class which takes a const reference to the derived class explicitly (with a forward declaration). In my simple test app, this constructor gets called in the slicing case. You could then at least get a run-time assertion, and you could probably get a compile-time assertion with clever use of templates (eg: instantiate a template in a way which generates a compile-time assertion in that constructor). There may also be compiler-specific ways to get compile time warnings or errors when you call explicit functions; for example, you can use "__declspec(deprecated)" for the "slice constructor" in Visual Studio to get a compile-time warning, at least in the function-call case.

    So in your example, the code would look like this (for Visual Studio):

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

    This works in my test (compile-time warning). Note that it doesn't solve the copy case, but a similarly-constructed assignment operator should do the trick there.

    Hope this helps. :)

    0 讨论(0)
  • 2021-02-04 04:05

    This is commonly called Object Slicing and is a well-known enough problem to have its own Wikipedia article (although it is only a short description of the problem).

    I believe I have used a compiler that had a warning you could enable to detect and warn about this. However, I don't recall which one that was.

    0 讨论(0)
  • 2021-02-04 04:09

    As a variation of Andrew Khosravian's answer, I'll suggest using templated copy constructors and assignment operators. This way you don't need to know all the derived classes of a given base class in order to safeguard that base class against slicing:

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

    Although this reduces the amount of work needed, with this approach you still need to mess with each base class in order to safeguard it. Also, it will cause problems if there are legitimate conversions from Base to SomeOtherClass that employ an operator Base() member of SomeOtherClass. (In that case, a more elaborate solution involving boost::disable_if<is_same<T, SomeOtherClass> > can be used.) In any case, you should remove this code once you've identified all instances of object slicing.

    To the compiler implementors of the world: Testing for object slicing is definitely something that would be worthwhile creating (optional) warnings for! I can't think of one instance where it would be desired behaviour, and it's very commonly seen in newbie C++ code.

    [EDIT 27/3/2015:] As pointed out by Matt McNab, you actually don't need to declare the copy constructor and assignment operator explicitly as I've done above, as they will still be implicitly declared by the compiler. In the 2003 C++ standard, this is explicitly mentioned in footnote 106 under 12.8/2:

    Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor. Template constructors participate in overload resolution with other constructors, including copy constructors, and a template constructor may be used to copy an object if it provides a better match than other constructors.

    0 讨论(0)
提交回复
热议问题