[英]In-place initialization risks
Currently, I have a design that maps objects by a key, in an unordered_map
. 目前,我有一个设计,通过一个键在
unordered_map
中映射对象。 The problem is that in the constructor of this object, I need to look it up by the key- even though it doesn't exist yet. 问题是在这个对象的构造函数中,我需要通过键来查找它 - 即使它还不存在。 So far I have solved this problem by deferring everything, but it's quite awkward.
到目前为止,我通过推迟一切来解决这个问题,但这很尴尬。
So I've been considering a kind of in-place-initializer. 所以我一直在考虑使用一种就地初始化器。 Something like
就像是
std::unordered_map<K, std::unique_ptr<T, FunkyDeleter>> map;
T* ptr = malloc(sizeof(T));
map[key] = std::unique_ptr<T, FunkyDeleter>(ptr);
try {
new (ptr) T(args);
} catch(...) {
map[key].release();
map.erase(key);
free(ptr);
throw;
}
This way, code in T's constructor can look it up in the map, even though it's not fully constructed yet. 这样,T的构造函数中的代码可以在地图中查找,即使它还没有完全构造。
What are the risks and problems inherent in this design? 这种设计固有的风险和问题是什么? So far I identified exception safety, the destructor for the
unique_ptr
is gonna be awkward, as well as the risks of accessing a half-constructed T
. 到目前为止,我确定了异常安全性,
unique_ptr
的析构函数会很尴尬,以及访问半结构T
的风险。
Edit: 编辑:
Roughly speaking, T represents a node in a graph which is very definitely not acyclic and never will be acyclic, ever. 粗略地说,T表示图中的一个节点,它绝对不是非循环的,永远不会是非循环的。 In T's constructor, to calculate some things about T, I wanted to look at T's subnodes- which can contain direct references to that T instance.
在T的构造函数中,为了计算关于T的一些事情,我想看一下T的子节点 - 它们可以包含对该T实例的直接引用。 Imagine something like
想象一下
struct K {
std::vector<K*> subkeys;
};
class T {
std::vector<T*> child_nodes;
public:
T(K* key, graph& graph) {
for(auto subkey : keys->subkeys)
child_nodes.push_back(graph.get(subkey));
}
std::vector<T*> children() { return child_nodes; }
};
class graph {
std::unordered_map<K*, std::unique_ptr<T>> nodes;
public:
T* get(K* key) {
if (nodes.find(key) == nodes.end())
nodes[key] = std::unique_ptr<T>(new T(key, *this));
return nodes[key].get();
}
};
int main() {
graph g;
K key1;
K key2;
key1.subkeys.push_back(&key2);
key2.subkeys.push_back(&key1);
g.get(&key1);
}
This obviously doesn't work in the case of cyclic references in K objects. 这显然不适用于K对象中的循环引用。 The problem is how I'm going to support them.
问题是我将如何支持他们。 So far I simply deferred all work so that
T
simply does not evaluate any potentially referencing code in the constructor, but that leads to some very awkward designs in some places. 到目前为止,我只是推迟了所有工作,以便
T
不会在构造函数中评估任何可能的引用代码,但这会在某些地方导致一些非常尴尬的设计。 I wanted to try and place the pointer to the T into the map as it's being constructed, so that circular references evaluate correctly in the constructor and I can throw out this deferred work, as some of it actually has important side-effects (which I cannot avoid due to a third-party design) and managing deferred side-effects is a bitch. 我想尝试将指针放到地图中,因为它正在构造中,因此循环引用在构造函数中正确评估,我可以抛弃这个延迟的工作,因为其中一些实际上有重要的副作用(我由于第三方设计而无法避免)并且管理延迟的副作用是一个婊子。
When you have such tight dependencies, in general constructor and destructor easily become a mess. 当你有这种紧密的依赖关系时,一般的构造函数和析构函数很容易变得一团糟。
In general, you should avoid such things. 一般来说,你应该避免这样的事情。 Sometimes, it's just clearer to construct objects in an invalid state, and then initialize them with an
initialize
method. 有时,在无效状态下构造对象更简洁,然后使用
initialize
方法初始化它们。 If you have some instance fields that you would like to be const
, you can group them in a non-const struct
field with const
fields, that it assigned by initialize
. 如果你有一些你想成为
const
实例字段,你可以将它们组合在一个带有const
字段的非const struct
字段中,它由initialize
分配。 You can even define some form of optional
which can never be assigned more than once, but maybe that's overshooting. 你甚至可以定义某种形式的
optional
,它永远不会被分配多次,但也许这就是超调。
Getting back to the question, what I see is: 回到这个问题,我看到的是:
unique_ptr
will call delete
on a pointer which was not allocated with new
, which is undefined behavior. unique_ptr
的析构函数将在未分配new
的指针上调用delete
,这是未定义的行为。 You should use a unique_ptr with a dedicated/noop deallocator, unless you can control all the code which might remove that thing from the map, and be sure it uses that same release-erase-free
boilerplate you used in your catch
clause. catch
子句中使用的相同的release-erase-free
样板。 unique_ptr
is meant to also work with incomplete types, so it will not try to access your object, and your T*
is segregated inside the function. unique_ptr
也适用于不完整类型,因此它不会尝试访问您的对象,并且您的T*
在函数内部被隔离。 So you can only access a partially constructed T
if: T
:
T
's constructor itself leaks a reference (this would be independent from your code here), or T
的构造函数本身泄漏了一个引用(这将独立于你的代码),或者 In response to your edit. 响应您的编辑。 First of all, it seems clean to me.
首先,它对我来说似乎很干净。 Doesn't look like you're doing something weird.
看起来你不是在做一些奇怪的事情。 But you have to deal with the destructor problem, because your map will eventually get destroyed (program termination also causes destructors to be called).
但是你必须处理析构函数问题,因为你的映射最终会被破坏(程序终止也会导致析构函数被调用)。
Anyway: 无论如何:
T
constructor to check the subkey
before the lookup, and avoid looking for its own key
? T
构造函数来检查subkey
,并避免查找自己的key
吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.