Clarification of exact meaning of object.lifetime and P0137

33 views
Skip to first unread message

Richard Hodges

unread,
Dec 2, 2016, 6:52:59 AM12/2/16
to std-dis...@isocpp.org
Dear all,

I am very sorry if this is old ground. I am sure it has been the subject of much debate amongst people in the know.

The question is along the lines of “when is an object not an object”.

Please kindly see the code in the linked stack-overflow question.

I believe that all code here is conforming as per my most meticulous reading of the iso docs available to me.

If there is some misunderstanding I would be grateful for the feedback (or link to same).


Here are the questions:

A. If I have misunderstood the standard, and there is any UB here, please kindly point it out (or alternatively confirm that there is no UB)

B. Are the constructor/destructor calls at A, B, C, D, E, F strictly necessary in light of the fact that the construction is trivial, and performs no actual initialisation. If so, please indicate which part of the standard contradicts or clarifies object.lifetime 2.2 in this regard.


Here’s the code

The three functions do the following:

create1() - create a unique_ptr to Foo controlled by the lifetime of an unsigned char array allocated with new []
create2() - create a unique_ptr to Foo controlled by the lifetime of an unsigned char array allocated with malloc()
provide() - create a shared_ptr to Foo controlled by the lifetime of a a statically-initialised  unsigned char array
 

#include <memory>

// a POD with trivial constructor
struct Foo 
{
  int x;
};

struct destroy1
{
  void operator()(Foo* p)
  {
    auto memory = std::unique_ptr<unsigned char[]>(reinterpret_cast<unsigned char*>(p));
    p->~Foo(); // A
  }
};
std::unique_ptr<Foo, destroy1> create1()
{
  auto p = std::make_unique<unsigned char[]>(sizeof(Foo));
  auto pCandidate = reinterpret_cast<Foo*>(p.get());
  new (pCandidate) Foo(); // B
  return std::unique_ptr<Foo, destroy1>(reinterpret_cast<Foo*>(p.release()), 
                                        destroy1());
}

struct call_free
{
  void operator()(void *p) const { std::free(p); } 
};
using malloc_ptr = std::unique_ptr<unsigned char, call_free>;

struct destroy2
{
  void operator()(Foo *pfoo) const {
    auto memory = malloc_ptr(reinterpret_cast<unsigned char*>(pfoo));
    pfoo->~Foo(); // C
  }
};

std::unique_ptr<Foo, destroy2> create2()
{
  auto p = malloc_ptr(reinterpret_cast<unsigned char*>(std::malloc(sizeof(Foo))));
  auto pCandidate = reinterpret_cast<Foo*>(p.get());
  new (pCandidate) Foo(); // D
  return std::unique_ptr<Foo, destroy2>(reinterpret_cast<Foo*>(p.release()), 
                                        destroy2());
}

struct nodelete {
  void operator()(Foo * p) {
    p->~Foo();  // E
  }
};

std::shared_ptr<Foo> provide()
{
  alignas(Foo) static unsigned char  storage[sizeof(Foo)];

  auto make = [] {
    auto p = reinterpret_cast<Foo*>(storage);
    new (p) Foo (); // F
    return std::shared_ptr<Foo>(p, nodelete());
  };

  static std::shared_ptr<Foo> pCandidate = make();

  return pCandidate;
}


int main()
{
  auto foo1 = create1();
  auto foo2 = create2();
  auto foo3 = provide();

  foo1->x = 1;
  foo2->x = 2;
  foo3->x = 3;
}

Reply all
Reply to author
Forward
0 new messages