用于临时延长生命周期的 const 参考

发布于 2024-09-30 14:04:57 字数 2248 浏览 2 评论 0原文

我对某些 C++ 标准合规性或缺乏标准有疑问。

在我的项目中,我使用一些简单的 Guard 类,该类使用 const 引用技巧。我使用的是 Visual Studio 2005,有两种配置 - 一种用于正常发布构建,另一种用于单元测试。

在这两种情况下,最终都会暂时挂在 const 引用上,但同时发生的事情才是问题所在。对于发布配置,const 引用直接指向创建 Guard 实例的辅助函数模板的返回中创建的 temp(不调用复制构造函数,甚至没有实例化)。

但对于单元测试conf,首先复制函数模板temp,然后调用其析构函数,仅在const引用超出范围后执行应执行的操作。

我通过禁用基类复制构造函数中的原始保护解决了这个问题(因此,对于调用复制构造函数的配置,不会触发析构函数中的操作),但令我困扰的是:

复制临时行为是标准的吗?符合吗?标准是否规定 const 引用应直接指向 temp,或者标准中未指定这种实现定义的行为?

我的代码大致基于 DDJ 中的 Scope Guard 文章和 Herb Sutter 的 gotw 88 文章,但这两个来源似乎都没有考虑早期的析构函数调用。

来自知识渊博的人的任何信息将不胜感激。

编辑:

好的,代码是这样的:

class GuardBase
{
public:

  GuardBase() : m_enabled(true)
  {}

  //this is done because in normal build no copy constructor is called ( directly using the function temporary)
  //but for UT conf somehow the original temp is copied and destroyed
  GuardBase(const GuardBase& other)
  {
    other.disable();
  }

  void disable() const
  {
    m_enabled = false;
  }

protected:
  //member is mutable because we will access the object through the const reference
  mutable bool m_enabled;
};

template< typename Arg, typename ObjType, typename MemberMethod >
class Guard1Arg : public GuardBase 
{
public:
  Guard1Arg(ObjType& obj, MemberMethod remover,  Arg arg) : m_arg(arg), m_remover(remover), m_object(obj)
  {}

  ~Guard1Arg()
  {
    if ( m_enabled )
    {
      (m_object.*m_remover)(m_arg);
    }
  }

private:
  Arg m_arg;
  MemberMethod m_remover;
  ObjType& m_object;

  //this class should not be assigned
  Guard1Arg& operator=(const Guard1Arg& other);

};

//utility template function used to create Guards using member functions with 1 argument
template<typename MemberFunction, typename Obj, typename Arg>
Guard1Arg<Arg, Obj, MemberFunction> MakeGuard1Arg(Obj& obj, MemberFunction memberFunction, Arg& arg)
{
  return Guard1Arg<Arg, Obj, MemberFunction>(obj, memberFunction, arg);
}


#define GUARD_CREATE(arg, remover) const GuardBase& guard = MakeGuard1Arg(*this,  remover, arg);
#define GUARD_DISABLE guard.disable();
#define GUARD_FRIEND template< typename Arg, typename ObjType, typename MemberMethod > friend class Guard1Arg;

I have a question about some C++ standard compliance or lack of it.

In my project I'm using some simple Guard class that uses the const reference trick. I'm using Visual Studio 2005 and there are two configurations - one for normal release build and the second one for unit tests.

In both cases there is some temporary hanging on the const reference in the end, but what happens in the meantime is the problem. For release configuration, the const reference points directly to the temp created in the return of the helper function template that creates the Guard instance ( no copy constructor is called, not even instantiated for that matter ).

But for unit test conf the function template temp is first copied and then its destructor is called, doing what should be done only after the const reference goes out of scope.

I have solved the problem by disabling the original guard in base class copy constructor ( so the action in destructor is not triggered for config for which copy constructor is called ), but what bothers me is:

Is the copy-the-temporary behaviour standard-compliant? Does the standard tells that the const reference should point directly to the temp, or is this implementation-defined behaviour not specified in the standard?

I have based my code roughly on Scope Guard article in DDJ and Herb Sutter's gotw 88 article, but both those sources don't seem to take the earlier destructor call into account.

Any info from someone more knowledgeable will be appreciated.

EDIT:

Ok the code is something like this:

class GuardBase
{
public:

  GuardBase() : m_enabled(true)
  {}

  //this is done because in normal build no copy constructor is called ( directly using the function temporary)
  //but for UT conf somehow the original temp is copied and destroyed
  GuardBase(const GuardBase& other)
  {
    other.disable();
  }

  void disable() const
  {
    m_enabled = false;
  }

protected:
  //member is mutable because we will access the object through the const reference
  mutable bool m_enabled;
};

template< typename Arg, typename ObjType, typename MemberMethod >
class Guard1Arg : public GuardBase 
{
public:
  Guard1Arg(ObjType& obj, MemberMethod remover,  Arg arg) : m_arg(arg), m_remover(remover), m_object(obj)
  {}

  ~Guard1Arg()
  {
    if ( m_enabled )
    {
      (m_object.*m_remover)(m_arg);
    }
  }

private:
  Arg m_arg;
  MemberMethod m_remover;
  ObjType& m_object;

  //this class should not be assigned
  Guard1Arg& operator=(const Guard1Arg& other);

};

//utility template function used to create Guards using member functions with 1 argument
template<typename MemberFunction, typename Obj, typename Arg>
Guard1Arg<Arg, Obj, MemberFunction> MakeGuard1Arg(Obj& obj, MemberFunction memberFunction, Arg& arg)
{
  return Guard1Arg<Arg, Obj, MemberFunction>(obj, memberFunction, arg);
}


#define GUARD_CREATE(arg, remover) const GuardBase& guard = MakeGuard1Arg(*this,  remover, arg);
#define GUARD_DISABLE guard.disable();
#define GUARD_FRIEND template< typename Arg, typename ObjType, typename MemberMethod > friend class Guard1Arg;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

画▽骨i 2024-10-07 14:04:57

这两种行为都符合标准。
如果您有这样的代码:

T foo()
{
  return T();
}

int main()
{
  const T& x = foo();
}

那么,从概念上讲,在 foo 中,将创建一个临时对象。该临时值被复制到 foo 的返回值。在 main 中,此副本(也是临时对象)绑定到 x
作为 foo 返回值的副本的生命周期得到延长,但作为副本源的临时副本的生命周期却没有延长。

但是,C++ 标准明确允许删除多余的临时对象。因此,foo 可以直接在该槽中创建临时值,而不是创建临时值并将其复制到槽中以获取返回值。
这两个选项都是可能的,编译器甚至不必记录何时使用哪个选项。

C++ 标准的相关部分是 6.6.3 ([stmt.return]) 和 12.2 ([class.temporary])。

Both behaviours are standards conforming.
If you have code like this:

T foo()
{
  return T();
}

int main()
{
  const T& x = foo();
}

Then, conceptually, in foo, a temporary object is created. This temporary is copied to the return-value of foo. In main, this copy (which is also a temporary object) is bound to x.
The copy that is the return-value from foo gets its lifetime extended, but not the temporary that was the source for the copy.

But, the C++ standard explicitly allows redundant temporary objects to be elided. So instead of creating a temporary and copying that over into the slot for return values, foo can directly create the temporary in that slot.
Both options are possible, and the compiler does not even have to document when it uses which option.

The relevant sections of the C++ standard are 6.6.3 ([stmt.return]) and 12.2 ([class.temporary]).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文