什么是最常用的方式来延迟"构建一个C ++对象?

时间:2016-07-27 03:51:19

标签: c++ c++11 constructor

对于内置类型,如int,您可以简单地写入任何内容来延迟初始化。有没有办法对C ++对象做同样的事情?

我编写了这个代码来完成这项工作,但我想知道是否有一种惯用的方法。如果是这样,它是什么?甚至在引入对齐存储之前是否可能?

#include <utility>
#include <type_traits>

template <typename T>
struct delayed {
    delayed() { unset_init(); }
    template <typename...Args> void init(Args&&... args) {
        new ( memory() ) T(std::forward<Args>(args)...);
        set_init();
    }

    operator T*() {
        return memory();
    }

    ~delayed() {
        if (get_init()) {
            memory()->~T();
            unset_init();
        }
    }

private:
    T* memory() { return reinterpret_cast<T*>(&bytes_); }
    unsigned char* raw_memory() { return reinterpret_cast<unsigned char*>(&bytes_); }

    unsigned char& init() { return *( raw_memory() + sizeof(T) ); }
    bool get_init() { return init() != 0; }
    void set_init() { init() = 1; }
    void unset_init() { init() = 0; }

    typename std::aligned_storage<sizeof(T) + 1, alignof(T)>::type bytes_{};
};

4 个答案:

答案 0 :(得分:14)

在C ++ 17及更高版本中,我希望首选的习语是std::optional<T>。在C ++ 11和C ++ 14中,似乎std::unique_ptr<T>很常见,但它有明显的缺点,需要堆分配。

用法:

std::optional<T> t; // initially empty
// do some stuff
// now we're ready to create the T value
t.emplace(foo, bar); // constructs the T with foo, bar as args

答案 1 :(得分:4)

首先,int变量是C ++对象。大概是当你谈论C ++对象而不是int时,你的意思是类类型对象。但不仅仅是类类型对象,因为你可以这样做:

struct Blah{ int x; int y; };

auto main() -> int
{
    Blah o;    // Uninitialized, indeterminate value.
    // Whatever
    o = {6, 7};
};

所以你可能意味着具有至少一个用户定义构造函数的类类型的对象

相对于用于访问它的事物的声明,延迟初始化此类对象的最常见方法包括

  • std::vector作为展开式数组,
  • 直接动态分配(无论如何管理生命周期)和
  • 重构代码,

...重构主要是将以后的使用代码移动到一个或多个函数。

例如,丑陋且低效的延迟初始化代码

unique_ptr<MyClass> p;

if( condition() )
{
    // Some code here, then
    p.reset( new MyDerivedA( 123 ) );
}
else
{
    // Some code here, then
    p.reset( new MyDerivedB( "andromeda" ) );
}
// Code using *p here.

......可能会被重构为

void foo( MyClass&& o )
{
    // Code using o here.
}

…
if( condition() )
{
    // Some code here, then
    foo( MyDerivedA( 123 ) );
}
else
{
    // Some code here, then
    foo( MyDerivedB( "andromeda" ) );
}

不太常见的方式包括

  • new放置在一些适当对齐的字节数组中,就像在代码中一样,

  • 如果您的班级是可移动的,请使用支持移动分配的Optional_班级(Barton-Nackman Fallible,Boost和C ++ 17 optional)。

我认为,这些技术是否可以被认为是推迟初始化的惯用语,是非常主观的个人观点。

答案 2 :(得分:0)

我不知道这样做的惯用方法 其他答案确实非常有趣(std::optional将成为标准库的一部分,但是它可以被认为是惯用的吗?)。
这是另一个基于另一个习语的延迟初始化的例子, pimpl (最小的,工作示例):

#include <memory>
#include <iostream>

struct I {
    void doSomething(int i) { val = i; }
    int getSomeData() { return val; }
    int val;
};

class C {
    static I& initialized(C &c) {
        std::cout << "is initialized" << std::endl;
        return *(c.impl);
    }

    static I& uninitialized(C &c) {
        std::cout << "was uninitialized" << std::endl;
        c.impl = std::make_unique<I>();
        c.getter = &initialized;
        return c.getter(c);
    }

public:
    C(): impl{nullptr}, getter{&uninitialized} {}

    void doSomething(int i) { getter(*this).doSomething(i); }
    int getSomeData() { return getter(*this).getSomeData(); }

private:
    using Getter = I&(*)(C &);
    std::unique_ptr<I> impl;
    Getter getter;
};

int main() {
    C c;
    c.doSomething(42);
    c.getSomeData();
}

在这种情况下,该类只不过是另一个类中包含的一堆数据和函数的包装器 通过延迟内部表示的构造,实际上是在推迟外部类的初始化。

答案 3 :(得分:0)

如果您可以使用潜在副本或移动而不是直接初始化,则可以使用联合。我希望optional来自std::experimental,C ++ 17或升压。

#include <iostream>

struct S {
    S(int, float) {std::cout << "S::S()" << std::endl;}
    ~S() {std::cout << "S::~S()" << std::endl;}
};

template<typename T>
union Delayed {
    bool initialized;
    T obj;

    Delayed(): initialized(false) {}
    ~Delayed() {}
};

int main() {
    Delayed<S> d;

    std::cout << 1 <<std::endl;

    d.obj = S(1, 1.0);

    return 0;
}