【问题标题】:Improper nesting of variable scopes in C++?C ++中变量范围的嵌套不正确?
【发布时间】:2011-07-20 15:53:06
【问题描述】:

我有一些看起来像这样的代码:

ComplexObject cpy;
{
  RAIILockObject _(obj->mutex);
  cpy = obj->org;
}
// use cpy

为了论证,假设ComplexObject 的默认构造函数开销很大。

  • C++ 编译器可以(并且可以)用复制构造函数替换 cpy 的默认构造/赋值吗?
  • 有什么方法可以重构代码以强制进行优化,同时保留两个本地对象的范围?

编辑:我真的在寻找一个通用的解决方案来解决希望将 RAII 对象与其他事物不正确嵌套的问题。

任何 cmet 对此采取 Konrad Rudolph 的解决方案?

ComplexObject = LockedInitInPlace(obj->org, obj->mutex);

template<class C> C LockedInitInPlace(C& c, Mutex& m) {
    RAIILockObject _(m);
    return c;
}

编辑 2:

原始代码有这样的顺序:

  1. 默认构造cpy
  2. 构造 RAII lock
  3. 将现有对象分配(复制)给cpy
  4. 破坏lock
  5. 使用cpy
  6. 破坏cpy

我想要的是:

  1. 构造 RAII lock
  2. 构造cpy(在这种情况下,通过使用现有对象的复制构造函数)。
  3. 破坏lock
  4. 使用cpy
  5. 破坏cpy

【问题讨论】:

  • 该死,我刚刚意识到如果复制本身很关键,我的解决方案将无法正常工作。如果不是(请澄清!),那么您的代码毫无意义。
  • @Konrad:对于我正在查看的案例类型,默认构造函数/赋值操作符对与复制构造函数相同,因此您的解决方案是有效的。但是,我感兴趣的是编译器实际上并不知道它的情况。

标签: c++ optimization raii scoping


【解决方案1】:

除非编译器可以向自己证明这种优化会导致相同的行为,否则不会。我真的无法想象编译器可以做到这一点的情况(给定互斥锁)。

这听起来很明显,但是你可以将默认构造函数更改为 not 成本高吗?这样的构造函数如果很容易被意外调用,很可能会在其他地方导致性能问题。

或者,您将不得不使用堆和指针(通过复制构造创建)而不是本地实例。

std::scoped_ptr<ComplexObject> cpyPtr = 0;
{
  RIAALockObject _(obj->mutex);
  cpyPtr = new ComplexObject(obj->org);
}
ComplexObject& cpy = *cpyPtr;  // create alias for ease of use.

【讨论】:

  • 如果你想避免动态分配,你可以使用boost::optional&lt;ComplexObject&gt;
【解决方案2】:

如果构造函数很复杂,则不太可能避免使用默认构造函数。

编译器可以做几乎任何事情,只要你的程序的可观察行为保持不变。

解决此问题的最佳方法是不要使ComplexObject 的默认构造函数变得昂贵。由于这个原因,使用昂贵的默认构造函数是不好的做法。

【讨论】:

  • 我知道这是针对这种情况的正确解决方案,但我希望有一个更通用的解决方案来解决不正确的范围对象嵌套问题。
【解决方案3】:

C++ 编译器可以(并且可以)用复制构造函数替换 cpy 的默认构造/赋值吗?

不,编译器被禁止这样做(假设你的类的默认构造函数足够复杂以至于编译器无法证明它的省略会导致一个等效的程序)。任何不符合标准的编译器。

编辑:以下解决方案有缺陷!不要使用它!

以下解决方案隐藏了竞争条件。 如果您的锁定是为了确保复制发生在关键部分,那么我的“解决方案”将打破这个假设,因为复制可能很好(并且可能会)发生在这个关键部分之外。它只有在你做其他工作时才有效。但在您的原始代码中,只有在复制本身很关键时,互斥锁才有意义。

只需执行以下操作即可防止默认构造:

ComplexObject = init(any_params_here);

ComplexObject init(any_params_here) {
    RAIILockObject _(obj->mutex);
    return obj->org;
}

感谢复制省略,这甚至不会执行不必​​要的复制,只需 一个(就像在您的代码中一样,但作为直接复制而不是复制分配)。

【讨论】:

  • 我还默默地假设你指的是 RAII,而不是美国唱片业协会。
  • 不禁止编译器做任何事情,只要可观察的行为保持不变: 1.9.1 "[...] 符合要求的实现需要模拟(仅)可观察的行为抽象机器 [...]”。如果在这种情况下默认构造 + 赋值与复制构造具有相同的行为,则编译器可以随意替换它们。事实上,它经常这样做。
  • @Peter,既然他说了,我想我记得 Konrad 是正确的,因为我要求的省略不涉及临时对象,而且我没有将其限制在以下情况编译器将确保可以访问构造函数或赋值函数。
  • @BCS:如果是这样,那么是的,这种优化不太可能发生,但仍然不是不可能的。不管怎样,这样做肯定不会破坏一致性。
  • @Peter:同意,但我想要它。
猜你喜欢
  • 2013-05-03
  • 1970-01-01
  • 2015-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多