简体   繁体   中英

Can I apply the final keyword to a POD (standard-layout) struct in C++11 ? Should I?

In a C++ project that is full of objects (with proper behaviors) and relatively few of non-object-oriented structs (consisting of only data fields and no methods), I would like to prevent the accidental misuse of these structs, in which one might try to create a class inheriting from it.

According to my understanding, because these "POD" (plain old data) structs do not have a virtual destructor, it is not possible to properly delete a derived class object (if one is allowed to create it) via a pointer of the POD type.

This seems to make a good use case for the C++11 "final" keyword, which marks a class or struct as non-inheritable.

However, I wonder if the "final" keyword causes the struct to become non-POD?

I suspect that the standards documentation may have addressed this issue, but I am not intelligent enough to sift through the very long documentations to find out. Any useful pointers would be welcome.

Note: I am not interested in merely knowing that it passes the compilation of some compiler vendors. Passing compilation do not guarantee:

  • Whether the compiled code will execute correctly in all situations (especially when the technique is applied in a larger, more complex project),
  • Whether the C++ standards body intends this to be used in this way.
#include <iostream>
using namespace std;

struct Pod final
{
    int a;
    int b;
    int c;
};

#if 0
class FailsBecauseCannotDeriveFromFinalAnything : public Pod
{
};
#endif

class ContainsSomethingFinalAsMember
{
public:
    ContainsSomethingFinalAsMember() : pod() {}
private:
    Pod pod;
};

int main() 
{
    std::cout << std::is_pod < Pod > :: value << std::endl;
    return 0;
}

According to my understanding, because these "POD" (plain old data) structs do not have a virtual destructor, it is not possible to properly delete a derived class object (if one is allowed to create it) via a pointer of the POD type.

It's not possible given a raw pointer, but it is possible given a smart pointer object, such as std::shared_ptr or a std::unique_ptr with an appropriate deleter.

Since smart pointers were standardized, there are few excuses to continue following the poor practice of using the delete operator manually. Certainly, classes shouldn't be designed around compatibility with the delete operator. Each class interface should be designed with its particular uses in mind.

No, it's not a good practice to make every class either final or polymorphic.

However, I wonder if the "final" keyword causes the struct to become non-POD?

No, it's still POD. The requirements for POD are standard layout (in turn requiring no base class, no access specifiers between members, no virtual anything) and trivial special member functions (copy/move constructor, assignment operator, destructor).

However, the purpose of POD-ness is to allow you to use memcpy instead of properly constructing objects, which good C++ code would avoid doing.

Marking a class final does not change its POD status. Quoting C++11:

9 [class]:

6 A trivially copyable class is a class that:

  • has no non-trivial copy constructors (12.8),
  • has no non-trivial move constructors (12.8),
  • has no non-trivial copy assignment operators (13.5.3, 12.8),
  • has no non-trivial move assignment operators (13.5.3, 12.8), and
  • has a trivial destructor (12.4).

A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable. [ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base classes. —end note ]

7 A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

8 A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class . ...

10 A POD struct is a non-union class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). ...

12.1 [class.ctor]

5 ... A default constructor is trivial if it is not user-provided and if:

  • its class has no virtual functions (10.3) and no virtual base classes (10.1), and
  • no non-static data member of its class has a brace-or-equal-initializer, and
  • all the direct base classes of its class have trivial default constructors, and
  • for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.

Otherwise, the default constructor is non-trivial.

12.4 [class.dtor]

5 ... A destructor is trivial if it is not user-provided and if:

  • the destructor is not virtual,
  • all of the direct base classes of its class have trivial destructors, and
  • for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

Otherwise, the destructor is non-trivial.

12.8 [class.copy]

12 A copy/move constructor for class X is trivial if it is not user-provided and if — class X has no virtual functions (10.3) and no virtual base classes (10.1), and — the constructor selected to copy/move each direct base class subobject is trivial, and — for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;

otherwise the copy/move constructor is non-trivial.

25 A copy/move assignment operator for class X is trivial if it is not user-provided and if

  • class X has no virtual functions (10.3) and no virtual base classes (10.1), and
  • the assignment operator selected to copy/move each direct base class subobject is trivial, and
  • for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is trivial;

otherwise the copy/move assignment operator is non-trivial.

As you see, the definition of a POD struct does not take into account whether the class has the class-virt-specifier final .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM