对于内置类型,如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_{};
};
答案 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;
}