How to prohibit the construction of object?
Asked Answered
P

5

8

How can I prohibit the construction of an object? I mark = delete; all relevant special functions as follows:

struct A
{
    A() = delete;
    A(A const &) = delete;
    A(A &&) = delete;
    void * operator new(std::size_t) = delete;
    void operator delete(void *) = delete;
};
A x{};
A y = {};
A * z = ::new A{};

LIVE EXAMPLE

But x, y and *z can still exist. What to do? I am interested in both cases; static/stack allocation and heap allocation.

Pasteboard answered 15/10, 2015 at 10:28 Comment(17)
It's not that clear what you're trying to do. IFAIK there's no standard conforming way to create an object without having it constructed (for non POD types).Manganite
@PiotrSkotnicki Pure virtual?Pasteboard
@Manganite Above code is correct from compilers point of view. Say, I just want to use static members of class.Pasteboard
add a private destructor to prevent creation of x & y. In the case of Z you explicitly called global operator new function. So it compiles fine.Ruthful
@PravasiMeet I know, but how to deny such a possibility?Pasteboard
@GregorMcGregor You are wrong coliru.stacked-crooked.com/a/b43ee3e03590be79 .Pasteboard
If you wan't to deny instantiation of the class you should use private constructors. Since you don't actually call the constructor you don't need to provide a body for it. This will make it impossible for two reasons to instantiatie the class.Manganite
@Orient I meant a non-pure-virtual destructor in place of operator new and operator delete. type A is an aggregate, so the value initialization becomes aggregate initialization, which doesn't call a default (deleted) constructorBiquadratic
@GregorMcGregor If you mean something like this, then you are right by all means =).Pasteboard
@GregorMcGregor Fine, it no matter though. Thank you for constructive critique.Pasteboard
This code is not valid C++. If clang compiles it, it's a problem of clang, not of C++. File a bug against clang. Gcc rejects the code as it should.Brahmin
@n.m. Your code shows x and y accepted however (which I don't see why)Overmeasure
@Orient x and y are static in this code, not "stack" (which they would be if inside a function)Overmeasure
@n.m. actually, I think this is a GCC bug. Those three forms should all be aggregate-initialization, which doesn't call the default-constructor.Bandoleer
@M.M. An implementation accepts or rejects entire programs, not individual lines. You are welcome to comment out an erroneous line and try again. If you have an example invalid program that gcc accepts, please share.Brahmin
@Bandoleer ouch you are right. It's an aggregate. My bad!Brahmin
@Overmeasure It was my mistake, sorry. The two lines arw actually valid. Only the third line with new is probably invalid.Brahmin
J
14

One option would be to give the class a pure virtual function, and mark it final:

struct A final
{
  virtual void nonconstructible() = 0;
};

[Live example]

Jahvist answered 15/10, 2015 at 10:39 Comment(6)
You can use also use pure virtual destructor make a class abstract instead of pure virtual function.Ruthful
@Bathsheba It was why I posted the answer at all (in addition to upvoting yours).Jahvist
Can someone explain what the purpose of having a final virtual class you can't instantiate is?Thermodynamic
@Hassan The OP asked for a class which you cannot construct. Ask them for their motives.Jahvist
Well I was just wondering whether there was a normal use for something like this, and I understand from your comment that the answer is no, there isn't.Thermodynamic
@Hassan The only thing I can think of mentioned by the OP in a comment - having an X with only static members, but being able to pass X as a template argument (which precludes using a namespace as X).Jahvist
S
6
  1. If you want to have just static members, then write namespace A rather than struct A. Ensuing code will be syntactically similar.

  2. To prevent creation of an instance of a class, make it abstract. (Include one pure virtual function). But doing this introduces a v-table into you class, which you might not want.

Sherwin answered 15/10, 2015 at 10:37 Comment(2)
The first solution has the disadvantage that you could not use it as a template parameter. The second solution has the disadvantage that it could be instantiated via inheritance.Manganite
But it can't be directly instantiated. @Angew has a solution for this which I am reluctant to plagiarise.Sherwin
M
6

If you want to make it impossible to instantiate the class you could just declare private constructors:

class NotInstantiable {
private:
    NotInstatiable();

public:
};

And not defining NotInstantiable further. This can't now be instantiated since first the constructor is private but also that a definition for the constructor has not been provided.

The second obstacle for instantiate the NotInstantiable would for example prohibit this possibility, which in fact otherwise is a well known pattern:

class NotInstantiable {
private:
    NotInstantiable();

public:
    NotInstantiable* evil_method()
    {
        return new NotInstantiable(); // this will fail if there's no body of the constructor.
    }
};
Manganite answered 15/10, 2015 at 10:39 Comment(1)
You stay to be right even if default c-tor stays defined in private section of class.Pasteboard
B
3

In general, to completely prevent client code instantiation of a class you can declare the class final and either

  • make the constructors non-public, or

  • delete the constructors and make sure that the class isn't an aggregate, or

  • add a pure virtual member function (e.g. make the destructor pure virtual) to make the class abstract.

Declaring the class final is necessary when the non-public is protected, and for the abstract class, in order to prevent instantiation of a base class sub-object of a derived class.


To partially prohibit instantiation, you can

  • make the destructor non-public.

This prevents automatic and static variables, but it does not prevent dynamic allocation with new.

  • make the class' allocation function (the operator new) non-public.

This prevents dynamic allocation via an ordinary new-expression in client code, but it does not provide automatic and static variables, or sub-objects of other objects, and it does not prevent dynamic allocation via a ::new-expression, which uses the global allocation function.

There are also other relevant techniques, such as an allocation function with extra arguments that make new-expressions inordinately complicated and impractical. I used that once to force the use of a special macro to dynamically allocate objects, e.g. for a shared-from-this class. But that was in the time before C++11 support for forwarding of arguments; nowadays an ordinary function can do the job, and such a function can be made a friend of the class.


The fact that the code compiles with at least one version of the clang compiler with -std=gnu++1z, is due to a bug and/or language extension in that compiler.

The code should not compile, since it invokes the default constructor that has been deleted. And it does not compile with e.g. MinGW g++ 5.1.0, even with -std=gnu++1z.

The fact that the code compiles with at least one version of the clang compiler with -std=gnu++1z, may be due to a bug and/or language extension in that compiler. What the correct behavior is, is unclear because

  • Although the code compiles with clang and with Visual C++ 2015, it does not compile with e.g. MinGW g++ 5.1.0, even with -std=gnu++1z.

  • Intuitively the delete would be meaningless if the code should compile, but many meaningless constructs are permitted in C++.

  • At issue is whether the class is an aggregate (in which case the new expression performs aggregate initialization), which rests on whether the deleted default constructor can be regarded as user-provided. And as user TartanLlama explains in comments, the requirements for user-provided are

C++11 §8.4.2/4

A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

I.e. although the delete of the default constructor in this question's example declares that constructor, it's not user-provided (and ditto for the other members) and so the class is an aggregate.

The only defect report I can find about this wording is DR 1355, which however just concerns an issue with the use of the words “special member”, and proposes to drop those words. But, considering both the effect demonstrated by this question, and considering that a function can only be deleted on its first declaration, the wording is strange.

Summing up, formally, as of C++11 (I haven't checked C++14), the code should compile. But this may be a defect in the standard, with the wording not reflecting the intent. And since MinGW g++ 5.1.0 doesn't compile the code, as of October 2015 it's not a good idea to rely on the code compiling.

Bartko answered 15/10, 2015 at 11:11 Comment(8)
Is this really ill-formed? I think A is an aggregate as it has no user-provided constructors or data-members, so those three forms should all be aggregate-initialization, which doesn't call the default-constructor.Bandoleer
@TartanLlama: By being deleted, the default constructor is declared.Bartko
@Alf but user-provided is defined as "user-declared and not explicitly defaulted or deleted on its first declaration".Bandoleer
Relevant standard entries are [dcl.fct.def.default] (/4 in C++11, /5 in C++14) and [dcl.init.aggr]/1Bandoleer
@TartanLlama: Oh. So if you're right then we're talking either a compiler bug in g++, or a defect in the standard. I'll have to check that out. Takes some time because I must do some other things. Sorry.Bartko
@Alf I think it's more likely a g++ bug, the standard seems pretty explicit that A is an aggregate and that all of those forms result in aggregate-initialization for appropriate types.Bandoleer
Hmm... actually the third case seems a bit unclear. New-initializers are specified to use direct-initialization rules. Not sure if that makes this valid or invalid.Bandoleer
@Tartan: All I find is DR 1355, which concerns the words "special member" in the definition of user-provided, and proposes to drop them. It looks like you're right wrt. the formal. But it seems unlikely that it was the intention that one should first declare a constructor and then delete it outside the class definition, in order to really delete it. On the third and gripping hand, the last time I submitted a defect report I never heard more about it. Updating the answer...Bartko
H
1

Essentially this compiles and is allowed because the type A is an aggregate type and the aggregate initialisation doesn't use default constructors.

What is an aggregate type?;

class type (typically, struct or union), that has

  • no private or protected members
  • no user-provided constructors (explicitly defaulted or deleted constructors are allowed) (since C++11)
  • no base classes
  • no virtual member functions

Giving it any one of the above would make it non-aggregate and thus the aggregate initialisation would not apply. Giving it a private user defined (and unimplemented) constructor will do.

struct A
{
    A() = delete;
    A(A const &) = delete;
    A(A &&) = delete;
    void * operator new(std::size_t) = delete;
    void operator delete(void *) = delete;
private:
    A(int);
};

As a side note; I hope this is a defect in the language specifications. At first look I thought that this should not compile, yet it does. One of the motivations for the =delete was to avoid the C++03 "trick" of declaring the constructors private to "hide" them and thus be unusable. I would expect a =delete on the default constructor to effectively prohibit class creation (outside other user defined constructors).


For easier reading and clearer intent, consider even an empty base class;

struct NonAggregate{};
struct A : private NonAggregate
{
    //...

Maybe the simplest yet is to return to the C++03 style here, make the default constructor private;

struct A
{
private:
    A(); // note no =delete...
};
Horseplay answered 15/10, 2015 at 12:27 Comment(8)
Having a private member prevents being aggregate. But any private member will do. Instead of treating the default constructor specially, which misleads a bit, I would just use an empty base class Non_aggregate.Bartko
@Cheersandhth.-Alf. That works to. Maybe makes the code easier to read as well. It's such a peculiar corner case though.Horseplay
You can make the deleted methods private, I don't think you need to add an extra A(int) ctor.Adrenocorticotropic
@MSalters. For two of the three construction test cases he has, apparently not. The private deleted constructors still compile. coliru.stacked-crooked.com/a/2389a83984c26f80.Horseplay
@MSalters. Removing the =delete still works (as it did in C++03).Horseplay
Oh well, it's all Bjarne's fault. There. I said it. :)Bartko
The issue with deleted default constructors is that struct A { const int x; }; (and also struct B { NonDefaultConstructible y; };)'s default constructor is deleted, but it's been an aggregate since forever.Included
@Included Good point that. Don't know what the best outcome here would be, it just is strange that the OP code sample compiles, yet it looks like it shouldn't. I wonder if the answer will eventually lie in that if it is explicitly or implicitly deleted, but again that brings its own issues with it (IIRC).Horseplay

© 2022 - 2024 — McMap. All rights reserved.