Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit destructor in templated context

I want to explicitly destroy a vector in a templated context. The following works for me (GNU C++ 4.3, 4.4 and Clang++ 1.1):

template <typename T>
void destroy_vector_owner(VectorOwner<T> *obj)
{
    obj->v.~vector();
    // further cleanup by Python API functions omitted
}

while it fails on Mac OS X v10.5's g++ (i686-apple-darwin10-gcc-4.2.1) with

expected class-name before ‘(’ token

If I change it to

obj->v.~vector<T>();

the code fails to compile with G++, but Clang can still handle it. Which is the correct idiom? Are any of these compilers known to be broken in this regard?

Update: the definition of VectorOwner is

template <typename T>
struct VectorOwner {
  PyObject_HEAD
  std::vector<T> v;
};

This is a Python object that has to keep an std::vector alive. I admit that the construct is slightly dangerous, but I need the compact storage, amortized O(1) push_back and the ability to steal another vector's contents with the swap member.

like image 534
Fred Foo Avatar asked Jul 27 '11 14:07

Fred Foo


People also ask

Can you explicitly call the destructor?

An explicit call to destructor is only necessary when an object is placed at a particular location in memory by using placement new. Destructor should not be called explicitly when the object is dynamically allocated because the delete operator automatically calls destructor.

Why destructor is used when delete is there?

When delete is used to deallocate memory for a C++ class object, the object's destructor is called before the object's memory is deallocated (if the object has a destructor). If the operand to the delete operator is a modifiable l-value, its value is undefined after the object is deleted.

How do you call a destructor template?

You can call the destructor as: object. ~TYPE();

What is trivial destructor in c++?

A trivial destructor is a destructor that performs no action. Objects with trivial destructors don't require a delete-expression and may be disposed of by simply deallocating their storage. All data types compatible with the C language (POD types) are trivially destructible.


2 Answers

My first answer was wrong actually, litb pointed my into the right direction. The right answer is that both syntaxes are correct:


Destructor call syntax.

The syntax for an explicit destructor call is described in 12.4 Destructors:

12  In an explicit destructor call, the destructor name appears
    as a ˜ followed by a type-name that names the destructor’s 
    class type. The invocation of a destructor is subject to the
    usual rules for member functions (9.3) [...]

type-name can be found in 7.1.5.2 Simple type specifiers:

type-name:
    class-name
    enum-name
    typedef-name

class-name is described in 9. Classes:

class-name:
    identifier
    template-id
    

So a destructor call is, simplified, one of the following

foo.~typedef-name ()
foo.~identifier   ()
foo.~template-id  ()

We neither have a typedef-name here, nor a simple identifier, so only foo.~template-id() is left for us.


Compiler's assumption on destructor call with template-arguments.

We also find in 14. Templates

3 After name lookup (3.4) finds that a name is a template-name,
  if this name is followed by a <, the < is always taken as the
  beginning of a template-argument-list and never as a name
  followed by the less-than operator.
  

So the compiler must assume in your example that the < is the beginning of a template-argument-list.

Also, if your destructor would be a template (...), then

4   When the name of a member template specialization appears 
    after . or -> in a postfix-expression, or after nested-name-specifier
    in a qualified-id, and the postfix-expression or qualified-id explicitly
    depends on a template-parameter (14.6.2), the member template name must
    be prefixed by the keyword template. Otherwise the name is assumed to 
    name a non-template.

So because you did not prefix your destructor call f.~foo<int> with template, i.e. like f.template ~foo<int>, the compiler must assume that your destructor is NOT a template.

Backtrack.

Further,

6   A template-id that names a class template specialization
    is a class-name (clause 9).

So ~foo<int> names your template specialization foo<int> and therefore is a class-name, a class-name is by the grammar rules a type-name, and a ~ followed by a typename is a destructor call. Therefore

foo<int> f;
f.~foo<int>(); // valid

Destructor call without template-arguments.

But also

f.~foo(); // valid

Because 3.4.5 Class member access:

3 If the unqualified-id is ˜type-name, and the type of the object expression
  is of a class type C (or of pointer to a class type C), the type-name is
  looked up in the context of the entire postfix-expression and in the scope of
  class C. [...]
  

thus in f.~foo();, foo is looked up within f., and within the scope of foo<int>, it is valid to refer to it just with with foo.


The standard is actually explicit on this topic, d'oh.

And finally, 14.3 contains the one-and-for-all-permission:

5   An explicit destructor call (12.4) for an object that 
    has a type that is a class template specialization may
    explicitly specify the template-arguments. [Example:

      template<class T> struct A {
          ˜A();
      };
      void f(A<int>* p, A<int>* q) {
          p->A<int>::˜A();      // OK: destructor call
          q->A<int>::˜A<int>(); // OK: destructor call
      }

    —end example]
like image 130
Sebastian Mach Avatar answered Oct 03 '22 07:10

Sebastian Mach


From n3290, 3.4.5 Class member access [basic.lookup.classref]

3 If the unqualified-id is ~type-name, the type-name is looked up in the context of the entire postfix-expression. If the type T of the object expression is of a class type C, the type-name is also looked up in the scope of class C. At least one of the lookups shall find a name that refers to (possibly cv-qualified) T. [...]

Following that is an example (as a non-normative note) which contains the following snippet of code:

a->~A(); // OK: lookup in *a finds the injected-class-name

In particular, for template<typename T, typename Allocator> class vector;, vector is the injected-class-name. For that reason, I believe

obj->v.~vector();

is correct.

(I don't have anything to say about ~vector<T> at the moment.)

like image 41
Luc Danton Avatar answered Oct 03 '22 07:10

Luc Danton