简体   繁体   English

就地初始化风险

[英]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: 回到这个问题,我看到的是:

  1. The destructor of 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. 您应该使用带有专用/ noop解除分配器的unique_ptr,除非您可以控制可能从映射中删除该东西的所有代码,并确保它使用您在catch子句中使用的相同的release-erase-free样板。
  2. 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的构造函数本身泄漏了一个引用(这将独立于你的代码),或者
    • Someone other thread tries to lookup for the new item. 其他人尝试查找新项目。 Doesn't seem to be the case, if your map is also local to the function. 如果您的地图也是函数的本地地图,那么似乎并非如此。 If you're doing multithread, then you must change your design, unless performance are not important. 如果你正在做多线程,那么你必须改变你的设计,除非性能不重要。 Because the only thing to do here is a reentrant mutex lock on the map, which would destroy all benefits you could get from multithreading. 因为这里唯一要做的就是在地图上使用可重入的互斥锁,这会破坏您从多线程中获得的所有好处。

EDIT 编辑

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: 无论如何:

  1. I think you're using keys in a weird way. 我认为你是以一种奇怪的方式使用键。 If the key itself already contains information about the subnodes, why are you also duplicating that information in the node itself? 如果密钥本身已包含有关子节点的信息,为什么还要在节点本身中复制该信息? Information duplication calls for out-of-sync-data bugs. 信息复制需要不同步数据错误。
  2. Can't you just change 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM