Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't a class having private constructor prevent inheriting from this class? How to control which classes can inherit from a certain base?

class B {
private:
    friend class C;
    B() = default;
};

class C : public B {};
class D : public B {};

int main() {
    C {};
    D {};
    return 0;
}

I assumed that since only class C is a friend of B, and B's constructor is private, then only class C is valid and D is not allowed to instantiate B. But that's not how it works. Where am I wrong with my reasoning, and how to achieve this kind of control over which classes are allowed to subclass a certain base?

Update: as pointed out by others in the comments, the snippet above works as I initially expected under C++14, but not C++17. Changing the instantiation to C c; D d; in main() does work as expected in C++17 mode as well.

like image 814
Violet Giraffe Avatar asked Apr 05 '19 12:04

Violet Giraffe


People also ask

Can a class with private constructor be inherited Why?

If a class has one or more private constructor and no public constructor then other classes are not allowed to create instance of this class; this means you can neither create the object of the class nor can it be inherited by other classes.

Can we inherit class with private constructor in Java?

Java doesn't prevent sub-classing of class with private constructors. What it prevents is sub-classes which cannot access any constructors of its super class. This means a private constructor cannot be used in another class file, and a package local constructor cannot be used in another package.

Can we inherit a class with private constructor in C++?

You can't inherit from a base-class whose only constructor is private. So make the base-class constructor public/protected, or add another base-class constructor.

Can we inherit from private class?

Members of a class that are declared private are not inherited by subclasses of that class. Only members of a class that are declared protected or public are inherited by subclasses declared in a package other than the one in which the class is declared. The answer is No. They do not.


1 Answers

This is a new feature added to C++17. What is going on is C is now considered an aggregate. Since it is an aggregate, it doesn't need a constructor. If we look at [dcl.init.aggr]/1 we get that an aggregate is

An aggregate is an array or a class with

  • no user-provided, explicit, or inherited constructors ([class.ctor]),

  • no private or protected non-static data members (Clause [class.access]),

  • no virtual functions, and

  • no virtual, private, or protected base classes ([class.mi]).

[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors.  — end note ]

And we check of all those bullet points. You don't have any constructors declared in C or D so there is bullet 1. You don't have any data members so the second bullet doesn't matter, and your base class is public so the third bullet is satisfied.

The change that happened between C++11/14 and C++17 that allows this is that aggregates can now have base classes. You can see the old wording here where it expressly stated that bases classes are not allowed.

We can confirm this by checking the trait std::is_aggregate_v like

int main()
{
    std::cout << std::is_aggregate_v<C>;
}

which will print 1.


Do note that since C is a friend of B you can use

C c{};
C c1;
C c2 = C();
    

As valid ways to initialize a C. Since D is not a friend of B the only one that works is D d{}; as that is aggregate initialization. All of the other forms try to default initialize and that can't be done since D has a deleted default constructor.

like image 163
NathanOliver Avatar answered Oct 19 '22 20:10

NathanOliver