Dealing with protected/private constructor/destruc

2019-05-14 01:24发布

Consider the following code:

#include <iostream>
#include <type_traits>

// Abstract base class
template<class Crtp>
class Base
{
    // Lifecycle
    public: // MARKER 1
        Base(const int x) :  _x(x) {}
    protected: // MARKER 2
        ~Base() {}

    // Functions
    public:
        int get() {return _x;}
        Crtp& set(const int x) {_x = x; return static_cast<Crtp&>(*this);}

    // Data members
    protected:
        int _x;
};

// Derived class
class Derived
: public Base<Derived>
{
    // Lifecycle
    public:
        Derived(const int x) : Base<Derived>(x) {}
        ~Derived() {}
};

// Main
int main()
{
    Derived d(5);
    std::cout<<d.set(42).get()<<std::endl;
    return 0;
}

If I want a public inheritance of Derived from Base, and if I don't want a virtual destructor in the base class, what would be the best keywords for the constructor (MARKER 1) and the destructor (MARKER 2) of Base to guarantee that nothing bad can happen ?

3条回答
老娘就宠你
2楼-- · 2019-05-14 01:58

There's no problem, since the destructor is protected it means that client code can't delete a pointer to Base, so there's no problem with Base's destructor being non-virtual.

查看更多
Lonely孤独者°
3楼-- · 2019-05-14 02:02

While your code works I find it odd to mark the destructor rather than the constructor as protected. Normally my reasoning would be that you want to prevent the programmer from accidentally creating a CRTP base object. It all comes down to the same of course, but this is hardly canonical code.

The only thing that your code prevents is the accidental deletion of a CRTP object via a base pointer – i.e. a case like this:

Base<Derived>* base = new Derived;
delete base;

But that is a highly artificial situation that won’t arise in real code since CRTP simply isn’t supposed to be used that way. The CRTP base is an implementation detail that should be completely hidden from the client code.

So my recipe for this situation would be:

  • Define a protected constructor.
  • Don’t define a destructor – or, if required for the CRTP semantic, define it as public (and non-virtual).
查看更多
等我变得足够好
4楼-- · 2019-05-14 02:12

Whatever programming style you use, you can alwyas do something bad: even if you follow the best of the bestest guideline practice. That's something physical behind it (and relate to the impossibility to reduce the global entrophy)

That said, don't confuse "classic OOP" (a methodology) with C++ (a language), OOP inheritache (a relation) with C++ inheritance (an aggregation mechanism) and OOP polymorphism (a model) with C++ runtime and static polymorphism (a dispatching mechanism).

Although names sometime matches, the C++-things don't have to necessarily sevicing OOP-things.

Public inheritance from a base with some non-virtual methods is normal. and destructor is not special: just dont call delete on the CRTP base.

Unlike with classic OOP, a CRTP-base has different type for each of the deriveds, so having a "pointer to a base" is clueless since there is no "pointer to a common type". And hence the risk to call "delete pbase" is very limited.

The "protected-dtor paradigm" is valid only if you are programming OOP-inheritance using C++ inheritance for object managed (and deleted) though pointer-based polymorphism. If you are following other paradigms, those rules should not be treated in a literal way.

In your case, the proteced-dtor just deny you to create a Base<Derived> on the stack and to call delete on a Base*. Something you will never do, since Base with no "Dervied" has no sense to exist, and having a Base<Derived>* makes no sense since you can have just a Derived*, hence having both public ctor and dtor makes no particular mess.

But you can even do the opposite choice to have both ctor and dtor protected, since you will never construct a Base alone, since it always needs a Derived type to be known.

Because of the particular construction of CRTP, all the classical OOP stuff leads to a sort of "indifferent equilibrium", since there is no more the "dangerous usecase".

You can use them or not, but no particular bad-thing can happen. Not if you use object the way they had been designed to be used.

查看更多
登录 后发表回答