Is there a better way in C ++ 11 to create classes on the stack

If I have two classes D1 and D2, which are both derived from the Base class, and I want to build a specific one based on, say, a Boolean variable, there are various well-known methods, for example, use factory or use smart pointers.

For instance,

    std::unique_ptr<Base> b;
    if (flag)
    {
            b.reset(new D1());
    }
    else
    {
            b.reset(new D2());
    }

But it uses a bunch to allocate, which is okay, but I can think of times when it would be nice to avoid the impact of memory allocation performance.

I tried:

Base b = flag ? D1() : D2();      // doesn’t compile

Base& b = flag ? D1() : D2();     // doesn’t compile

Base&& b = flag ? D1() : D2();    // doesn’t compile

Base&& b = flag ? std::move(D1()) : std::move(D2());   // doesn’t compile

My intention is that D1 or D2 is selected on the stack, and its lifetime ends when b goes beyond. Intuitively, I feel that there must be a way to do this.

I played with lambda functions and found that this works:

Base&& b = [j]()->Base&&{
                 switch (j)
                 {
                 case 0:
                       return std::move(D1());
                 default:
                       return std::move(D2());
                 }
          }();

, , , . , , , , , , . std:: move, !

, , , , :

  • , ( )
  • ,

?

+4
6

, . std::move .

, , .

void f(bool flag)
{
   Base &b = // some magic to choose which derived class to instantiate

   // do something with b
}

void doSomethingWith(Base &b)
{
   // do something with b
}

void f(bool flag)
{
  if (flag) {
    D1 d1;
    doSomethingWith(d1);
  }
  else {
    D2 d2;
    doSomethingWith(d2);
  }
}

, , , :

#include <iostream>

using std::cerr;

struct Base {
  virtual ~Base() { }
  virtual const char* name() = 0;
};

struct Derived1 : Base {
  Derived1() { cerr << "Constructing Derived1\n"; }
  ~Derived1() { cerr << "Destructing Derived1\n"; }
  virtual const char* name() { return "Derived1"; }
};

struct Derived2 : Base {
  Derived2() { cerr << "Constructing Derived2\n"; }
  ~Derived2() { cerr << "Destructing Derived2\n"; }
  virtual const char* name() { return "Derived2"; }
};

template <typename B,typename D1,typename D2>
class Either {
  union D {
    D1 d1;
    D2 d2;
    D() { }
    ~D() { }
  } d;
  bool flag;

  public:
    Either(bool flag)
      : flag(flag)
    {
      if (flag) {
        new (&d.d1) D1;
      }
      else {
        new (&d.d2) D2;
      }
    }

    ~Either()
    {
      if (flag) {
        d.d1.~D1();
      }
      else {
        d.d2.~D2();
      }
    }


    B& value()
    {
      if (flag) {
        return d.d1;
      }
      else {
        return d.d2;
      }
    }
};

static void test(bool flag)
{
  Either<Base,Derived1,Derived2> either(flag);

  Base &b = either.value();

  cerr << "name=" << b.name() << "\n";
}

int main()
{
  test(true);
  test(false);
}

:

    Constructing Derived1                                                           
    name=Derived1                                                                   
    Destructing Derived1                                                            
    Constructing Derived2                                                           
    name=Derived2                                                                   
    Destructing Derived2  
+3

, Boost.Variant, :

class Manager
{   
    using variant_type = boost::variant<Derived1, Derived2>;

    struct NameVisitor : boost::static_visitor<const char*>
    {   
        template<typename T>
        result_type operator()(T& t) const { return t.name(); }
    };  

public:
    template<typename T>
    explicit Manager(T t) : v_(std::move(t)) {}

    template<typename T>
    Manager& operator=(T t)
    { v_ = std::move(t); return *this; }

    const char* name()
    { return boost::apply_visitor(NameVisitor(), v_); }

private:
        variant_type v_; 

};  

: .

+5

, std::aligned_storage. - :

// use macros for MAX since std::max is not const-expr
std::aligned_storage<MAX(sizeof(D1), sizeof(D2)), MAX(alignof(D1), alignof(D2))> storage;
Base* b = nullptr;

if (flag)
  b = new (&storage) D1();
else
  b = new (&storage) D2();

aligned_storage, / , . aligned_storage - , ++ 98. :

template <typename T1, typename T2>
class storage
{
  union
  {
    double d; // to force strictest alignment (on most platforms)
    char b[sizeof(T1) > sizeof(T2) ? sizeof(T1) : sizeof(T2)];
  } u;
};

/, . Boost.Variant .

, ( ), , . RAII , , , .

template <typename T1, typename T2>
class storage
{
  using deleter_t = void(*)(void*);
  std::aligned_storage<
    sizeof(T1) > sizeof(T2) ? sizeof(T1) : sizeof(T2),
    alignof(T1) > alignof(T2) ? alignof(T1) : alignof(T2)
  > space;
  deleter_t deleter = nullptr;
public:
  storage(const storage&) = delete;
  storage& operator=(const storage&) = delete;
  template <typename T, typename ...P>
  T* emplace(P&&... p)
  {
    destroy();
    deleter = [](void* obj){ static_cast<T*>(obj)->~T(); }
    return new (&space) T(std::forward<P>(p)...);
  }
  void destroy()
  {
    if (deleter != nullptr)
    {
      deleter(&space);
      deleter = nullptr;          
    }
  }
};

// usage:
storage<D1, D2> s;
B* b = flag ? s.emplace<D1>() : s.emplace<D2>();

, , ++ 98, ( emplace).

+2

B&&b = flag ? static_cast<B&&>(D1()) : static_cast<B&&>(D2());
+1

, , (, DestructorDecorator), (D1 D2). ~DestructorDecorator D1 D2.

0

, flag ?

, :

create_if, :

template <typename T, typename F, bool B> struct create_if {};

-, create_if true false :

template <typename T, typename F> struct create_if<T, F, true> { using type = T; };
template <typename T, typename F> struct create_if<T, F, false> { using type = F; };

:

create_if<D1, D2, true>::type da;  // Create D1 instance
create_if<D1, D2, false>::type db; // Create D2 instance

You can change the logical literals using the compilation flag or using the function constexpr:

constexpr bool foo(const int i) { return i & 1; }
create_if<D1, D2, foo(100)>::type dc; // Create D2 instance
create_if<D1, D2, foo(543)>::type dd; // Create D1 instance

This is only valid if flagknown at compile time, I hope this helps.

Living example .

0
source

Source: https://habr.com/ru/post/1537701/


All Articles