什么是"延迟"构建C++对象的最惯用的方法是什么?

Pao*_*oni 7 c++ constructor c++11

对于内置类型,如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_{};
};
Run Code Online (Sandbox Code Playgroud)

Bri*_*ian 17

在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
Run Code Online (Sandbox Code Playgroud)

  • @ZanLynx:然后你必须使用只调用析构函数的删除器,而不是默认的调用`delete`的删除器. (3认同)
  • @underscore_d:我不确定这里的原始上下文:"但是你可以移动 - 赋值给它"是指Boost文档使用`boost :: optional`的默认构造函数的例子.很重要的是,我要求提供一个包含的例子,也就是说,那时没有例子.这个评论已被mod删除,总是改变历史... Brian然后包括移动分配的例子.然后Nicol Bolas提出了"emplace",答案的例子得到了更新.我不知道'emplace`,这确实是直接延迟建设. (2认同)

Che*_*Alf 6

首先,int变量是一个 C++ 对象。想必当您谈论 C++ 对象而不是 时int,您指的是类类型对象。但不仅仅是类类型对象,因为你可以这样做:

\n\n
struct Blah{ int x; int y; };\n\nauto main() -> int\n{\n    Blah o;    // Uninitialized, indeterminate value.\n    // Whatever\n    o = {6, 7};\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,您可能指的是至少具有一个用户定义的构造函数的类类型的对象

\n\n

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

\n\n
    \n
  • std::vector作为一个扩展数组,
  • \n
  • 直接动态分配(无论生命周期如何管理),以及
  • \n
  • 重构代码,
  • \n
\n\n

…重构本质上是将以后使用的代码移动到一个或多个函数中。

\n\n

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

\n\n
unique_ptr<MyClass> p;\n\nif( condition() )\n{\n    // Some code here, then\n    p.reset( new MyDerivedA( 123 ) );\n}\nelse\n{\n    // Some code here, then\n    p.reset( new MyDerivedB( "andromeda" ) );\n}\n// Code using *p here.\n
Run Code Online (Sandbox Code Playgroud)\n\n

…可能会被重构为

\n\n
void foo( MyClass&& o )\n{\n    // Code using o here.\n}\n\n\xe2\x80\xa6\nif( condition() )\n{\n    // Some code here, then\n    foo( MyDerivedA( 123 ) );\n}\nelse\n{\n    // Some code here, then\n    foo( MyDerivedB( "andromeda" ) );\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

不太常见的方式包括

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

  • \n
  • 如果您的类是可移动的,请使用Optional_类(Barton-Nackman Fallible、Boost 和 C++17optional)。

  • \n
\n\n

我认为,这些技术是否可以被视为延迟初始化的惯用方法,是相当主观的个人意见。

\n