28

In the following C++ code, I am allowed to explicitly call the destructor but not the constructor. Why is that? Wouldn't be explicit ctor call more expressive and unified with the dtor case?

class X { };

int main() {
  X* x = (X*)::operator new(sizeof(X));
  new (x) X;  // option #1: OK
  x->X();     // option #2: ERROR

  x->~X();
  ::operator delete(x);
}
curiousguy
  • 8,038
  • 2
  • 40
  • 58
Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • 2
    Looks like you're calling a member function on an uninitialized object, which is UB. – Mat Jul 17 '17 at 10:47
  • 2
    What does 'new (x) X' even mean? Why does it compile? Was it supposed to be 'new X(x)'? – patatahooligan Jul 17 '17 at 10:49
  • 5
    @patatahooligan: See **placement new** for details. It merely constructs the object, i.e., does not allocate memory. – Daniel Langr Jul 17 '17 at 10:50
  • Can you please clarify what is this syntax: `(X*)::operator new(sizeof(X));` and what does it do? – Ron Jul 17 '17 at 10:50
  • 1
    @Ron: It allocates `sizeof(X)` bytes of memory a the return pointer-to-void is just converted to pointer-to-X. – Daniel Langr Jul 17 '17 at 10:51
  • @DanielLangr See this https://isocpp.org/wiki/faq/dtors#calling-member-dtors If I find a reference to why explicitly calling a constructor never makes sense, we'll have a full answer. – patatahooligan Jul 17 '17 at 10:55
  • 16
    You can think of placement-new as being an explicit constructor call – M.M Jul 17 '17 at 11:03
  • Isn't `X x = X()` an explicit call of the constructor? It's only when combining with `new` that you need to jump through hoops to not do it in one expression. – Pelle Jul 17 '17 at 13:46
  • @Pelle `X x = X()` invokes two constructors: default and then copy – curiousguy Sep 14 '17 at 21:20
  • @curiousguy I think for most compilers `X x` and `X x = X()` are equivalent due to [copy elision](https://en.wikipedia.org/wiki/Copy_elision). – Scindix Sep 21 '18 at 16:02
  • @Scindix From C++17, copy (move) constructor even does not need to exist to write `X x = X();` (or, can be deleted, inaccessible, explicit). – Daniel Langr Sep 21 '18 at 16:06
  • @Scindix I prefer "objects fusion" or "lifetime fusion": the two objects with overlapping lifetimes are turned into one with the union of their lifetimes. – curiousguy Sep 21 '18 at 18:48

3 Answers3

38

Because before the constructor is started, there is no object of type X at that address. As such, dereferencing x as an X type or accessing members/methods of it would be Undefined Behavior.

So the major difference between x->X(); (hypothetical syntax) and x->~X() is that in the 2nd case you have an object on which you can call a (special) member such as the destructor, while in the first case, there is no object yet on which you can call methods (even the special method - constructor).

You could argue that there could be an exception to this rule, but then it ultimately would be a matter of syntax preference, where you have inconsistencies in both cases. With the current syntax the call to constructor doesn't look like a call to constructor, in your proposed syntax there would be symmetry with the destructor call, but inconsistencies in the rules which govern when you can dereference/access methods of an object. Actually there would have to be an exception allowing calling a method on something that is not a object yet. Then you would have to strictly define in the letter of the standard something that is not an object yet.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • It sounds reasonable. Sorry for such basic question, it didn't come to my mind when I was learning placement new stuff. – Daniel Langr Jul 17 '17 at 10:49
  • 24
    @DanielLangr don't apologize. **Question everything!** and if it is on-topic, you can ask here :) – bolov Jul 17 '17 at 10:54
  • Thank you for the clarification about possible exception. I was thinking about this as well. – Daniel Langr Jul 17 '17 at 10:54
  • 1
    Complementary marginal note: conceptually, the constructor really is a class method, while the destructor really is an instance method. – Matthias Jul 18 '17 at 10:18
  • @Matthias if by "class method" you mean static method and by "instance method" you mean non-static method then you are incorrect. The constructor is a "special member function" (§12 Special member functions [special]). Even without the standard, you can access non-static members within the constructor so you can easily see the constructor is a non-static member. – bolov Jul 18 '17 at 10:32
  • I know the specifics are different for C++, that's why I noted that "conceptually" a constructor is a class method, i.e. before it is invoked, no object exists (lifetime not started). In many dynamic languages you can "assign a class" to your data arbitrarily. Then a "constructor" performs this assignment. For C++ you can imagine that this assignment is performed under the hood as the first action performed by a constructor. Accessing (even private) members is no problem for a static method, btw. It just needs an object whose members it can access, e.g. the one it created itself. – Matthias Jul 18 '17 at 11:40
  • @Matthias that's a strange way to look at a constructor, but I guess if you are more familiar to dynamic languages like python it does make sense. But for `C++` I personally think it doesn't. – bolov Jul 18 '17 at 13:34
  • I agree, it probably depends on habits and opinion. Personally, I find it more convenient to think of constructors as class methods, because it a) makes classes feel like objects, and b) is a good argument for supporting class methods (you need them anyway). Some people argue against static methods, so I like to ask them: what is a constructor? Just to make sure I understand your POV, too: you think of constructors as special instance initializers (i.e. called after creation, not including creation)? – Matthias Jul 19 '17 at 14:25
  • "_Then you would have to strictly define in the letter of the standard something that is not an object yet_" You mean an object whose lifetime has not started yet? – curiousguy Sep 21 '18 at 20:23
9

This is a variation of the chicken-and-egg problem.

You can call destructors explicitly, as if they were member functions, because the instance of the object already exists.

You cannot do the same to a constructor, because an instance on which you would call it needs to exist, and be fully initialized by a constructor.

The only exception to this is when you have allocated memory for the object, but have not yet initialized the instance (i.e. the memory for the instance is there, but it has not been initialized to become an actual instance). Hence, you need to call a constructor. This is the situation when placement new, the syntax you show under "option 1" comment, is useful. However, this is not a member call that you perform on an instance, because the instance is not available prior to making that call.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • @aschepler I meant it as an exception to having an initialized instance, as opposed to a memory block sufficient to hold the instance. I edited to explain this in more details. – Sergey Kalinichenko Jul 17 '17 at 12:33
  • "_destructors ... as if they were member functions_" Destructors are not member functions? – curiousguy Sep 21 '18 at 19:11
  • @curiousguy Destructors share a great deal of features with member functions, but the special treatment provided by the compiler sets them apart from regular member functions. That’s why I would be reluctant calling destructors “member functions” with no further qualifications. – Sergey Kalinichenko Sep 21 '18 at 21:02
  • Destructors like constructors, assignment operator, can be generated implicitly. They are called automatically. They have special rules re: exception. They are treated specially in many ways but they aren't the only member functions treated specially. – curiousguy Sep 21 '18 at 22:03
-1

You can construct an object at an arbitrary place, by using a placement new.

The new() call can be overlaid with parameters; a placement constructor takes either a void*, or a pointer-to-type. The new() function always takes a parameter of size_t, which is sizeof() the type; this is normally only used by the global new function

The placement constructor, and explicate destructor are used when writing memory pools.

For example (from memory!)

class MyClass
{
    public:
       inline new(size_t size, MyClass *ptr) { return ptr; };
};

It is used like this

{
    MyClass *x = ...;
    MyClass *y = new (x) MyClass(construct parameters);
    x->~MyClass();
}

Edited to correct error pointed out by @Ben-Voigt

CSM
  • 1,232
  • 1
  • 8
  • 12
  • 1
    There's no such thing as a "placement constructor". A placement-new expression results in a call to one of the ordinary constructors (chosen via overload resolution). – Ben Voigt Jul 17 '17 at 19:55
  • I think you're missing the keyword `operator` before `new`. `inline` is also useless in this position. And why overload the placement `new` operator if you don't do anything different from the default one? – KABoissonneault Jul 17 '17 at 20:26
  • 1
    This explains what placement new is. It does not however answer the question in any way. – bolov Jul 18 '17 at 07:35