为 RAII 模板类编写对象生成器的更好方法?

发布于 2024-08-30 23:48:17 字数 1630 浏览 5 评论 0原文

我想编写一个 对象生成器 用于模板化 RAII 类——基本上是一个函数模板,用于使用参数的类型推导来构造对象,因此不必显式指定类型。

我预见的问题是,为我处理类型推导的辅助函数将按值返回对象,这将 (**) 导致在进行复制时过早调用 RAII 析构函数。也许 C++0x 移动语义可以有所帮助,但这对我来说不是一个选择。

有人以前见过这个问题并有好的解决方案吗?

这就是我所拥有的:

template<typename T, typename U, typename V>
class FooAdder
{
private:
  typedef OtherThing<T, U, V> Thing;
  Thing &thing_;
  int a_;
  // many other members
public:
  FooAdder(Thing &thing, int a);
  ~FooAdder();
  FooAdder &foo(T t, U u);
  FooAdder &bar(V v);
};

要点是 OtherThing 有一个糟糕的界面,而 FooAdder 应该让它更容易使用。预期用途大致如下:

FooAdder(myThing, 2)
  .foo(3, 4)
  .foo(5, 6)
  .bar(7)
  .foo(8, 9);

FooAdder 构造函数初始化一些内部数据结构。 foobar 方法填充这些数据结构。 ~FooAdder dtor 将事物包装起来并调用 thing_ 上的方法,处理所有麻烦的事情。

如果 FooAdder 不是模板,那就可以正常工作。但既然如此,我需要将类型放入其中,更像是这样:

FooAdder<Abc, Def, Ghi>(myThing, 2) ...

这很烦人,因为可以根据 myThing 推断出类型。因此,我更愿意创建一个模板化对象生成器,类似于 std::make_pair,它将为我进行类型推导。像这样的事情:

template<typename T, typename U, typename V>
FooAdder<T, U, V>
AddFoo(OtherThing<T, U, V> &thing, int a)
{
  return FooAdder<T, U, V>(thing, a);
}

这似乎有问题:因为它按值返回,所以堆栈临时对象将(**)被破坏,这将导致 RAII dtor 过早运行。

** - 如果未实施 RVO。大多数编译器都会这样做,但这不是必需的,可以使用 -fno-elide-constructors 在 gcc 中关闭。

I would like to write an object generator for a templated RAII class -- basically a function template to construct an object using type deduction of parameters so the types don't have to be specified explicitly.

The problem I foresee is that the helper function that takes care of type deduction for me is going to return the object by value, which will (**) result in a premature call to the RAII destructor when the copy is made. Perhaps C++0x move semantics could help but that's not an option for me.

Anyone seen this problem before and have a good solution?

This is what I have:

template<typename T, typename U, typename V>
class FooAdder
{
private:
  typedef OtherThing<T, U, V> Thing;
  Thing &thing_;
  int a_;
  // many other members
public:
  FooAdder(Thing &thing, int a);
  ~FooAdder();
  FooAdder &foo(T t, U u);
  FooAdder &bar(V v);
};

The gist is that OtherThing has a horrible interface, and FooAdder is supposed to make it easier to use. The intended use is roughly like this:

FooAdder(myThing, 2)
  .foo(3, 4)
  .foo(5, 6)
  .bar(7)
  .foo(8, 9);

The FooAdder constructor initializes some internal data structures. The foo and bar methods populate those data structures. The ~FooAdder dtor wraps things up and calls a method on thing_, taking care of all the nastiness.

That would work fine if FooAdder wasn't a template. But since it is, I would need to put the types in, more like this:

FooAdder<Abc, Def, Ghi>(myThing, 2) ...

That's annoying, because the types can be inferred based on myThing. So I would prefer to create a templated object generator, similar to std::make_pair, that will do the type deduction for me. Something like this:

template<typename T, typename U, typename V>
FooAdder<T, U, V>
AddFoo(OtherThing<T, U, V> &thing, int a)
{
  return FooAdder<T, U, V>(thing, a);
}

That seems problematic: because it returns by value, the stack temporary object will (**) be destructed, which will cause the RAII dtor to run prematurely.

** - if RVO is not implemented. Most compilers do, but it is not required, and can be turned off in gcc using -fno-elide-constructors.

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

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

发布评论

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

评论(7

等风来 2024-09-06 23:48:17

看起来很容易。提问者本人提出了一个很好的解决方案,但他可以只使用带有常量引用参数的普通复制构造函数。这是我在评论中提出的建议:

template<typename T, typename U, typename V>
class FooAdder
{
private:
  mutable bool dismiss;
  typedef OtherThing<T, U, V> Thing;
  Thing &thing_;
  int a_;
  // many other members
public:
  FooAdder(Thing &thing, int a);
  FooAdder(FooAdder const&o);
  ~FooAdder();
  FooAdder &foo(T t, U u);
  FooAdder &bar(V v);
};

FooAdder::FooAdder(Thing &thing, int a)
  :thing_(thing), a_(a), dismiss(false)
{ }

FooAdder::FooAdder(FooAdder const& o)
  :dismiss(false), thing_(o.thing_), a_(o.a_) 
{ o.dismiss = true; }

FooAdder::~FooAdder() {
  if(!dismiss) { /* wrap up and call */ }
}

它确实有效。

template<typename T, typename U, typename V>
FooAdder<T, U, V>
AddFoo(OtherThing<T, U, V> &thing, int a)
{
  return FooAdder<T, U, V>(thing, a);
}

int main() {
  AddFoo(myThing, 2)
    .foo(3, 4)
    .foo(5, 6)
    .bar(7)
    .foo(8, 9);
}

不需要复杂的模板或智能指针。

It seems pretty easy. The questioner himself proposed a nice solution, but he can just use a usual copy constructor with a const-reference parameter. Here is what i proposed in comments:

template<typename T, typename U, typename V>
class FooAdder
{
private:
  mutable bool dismiss;
  typedef OtherThing<T, U, V> Thing;
  Thing &thing_;
  int a_;
  // many other members
public:
  FooAdder(Thing &thing, int a);
  FooAdder(FooAdder const&o);
  ~FooAdder();
  FooAdder &foo(T t, U u);
  FooAdder &bar(V v);
};

FooAdder::FooAdder(Thing &thing, int a)
  :thing_(thing), a_(a), dismiss(false)
{ }

FooAdder::FooAdder(FooAdder const& o)
  :dismiss(false), thing_(o.thing_), a_(o.a_) 
{ o.dismiss = true; }

FooAdder::~FooAdder() {
  if(!dismiss) { /* wrap up and call */ }
}

It Just Works.

template<typename T, typename U, typename V>
FooAdder<T, U, V>
AddFoo(OtherThing<T, U, V> &thing, int a)
{
  return FooAdder<T, U, V>(thing, a);
}

int main() {
  AddFoo(myThing, 2)
    .foo(3, 4)
    .foo(5, 6)
    .bar(7)
    .foo(8, 9);
}

No need for complex templates or smart pointers.

自我难过 2024-09-06 23:48:17

您将需要一个工作副本构造函数,但标准中明确允许优化此类副本,并且应该是编译器进行的相当常见的优化。

我想说,这里可能很少需要担心移动语义(它可能无论如何都不起作用 - 请参阅 std::auto_ptr 所需的 auto_ptr_ref hackery) )。

You'll need a working copy constructor, but optimizing out such copies is explicitly allowed in the standard and should be quite a common optimization for compilers to make.

I'd say there's probably very little need to worry about the move semantics here (it is possible that it won't work anyway - see the auto_ptr_ref hackery that it takes for std::auto_ptr).

遮了一弯 2024-09-06 23:48:17

如果您想保证您想要做的事情在不使用移动语义的情况下也能工作,您需要执行 auto_ptr 所做的事情,即维护所有权状态并为在 < 之间转移所有权的类型提供转换运算符。代码>auto_ptrs。

在您的情况下:

  1. 添加一种机制来指示 FooAdder 中的所有权。在 FooAdder 的析构函数中,仅在拥有所有权时才调用清理函数。
  2. 私有化采用 const FooAdder & 的复制构造函数;这可以防止编译器在右值上使用复制构造函数,这会违反您的单一所有者不变式。
  3. 创建一个辅助类型(例如,FooAdderRef),用于在 FooAdders 之间转移所有权。它应该包含足够的信息来转移所有权。
  4. 将转换运算符(operator FooAdderRef)添加到 FooAdder,放弃 FooAdder 中的所有权并返回 FooAdderRef
  5. 添加一个构造函数,该构造函数接受 FooAdderRef 并声明其所有权。

如果您想查看实际的实现,这与 auto_ptr 的作用相同。它可以防止任意复制违反 RAII 约束,同时允许您指定如何从工厂函数转移所有权。

这也是 C++0x 具有移动语义的原因。因为否则它就是一个巨大的 PITA。

If you want to guarantee that what you want to do will work without using move semantics you need to do what auto_ptr does, which is maintain ownership state and provide a conversion operator to a type that transfers ownership between auto_ptrs.

In your case:

  1. Add a mechanism to indicate ownership in FooAdder. In FooAdder's destructor, only call the cleanup function if it has ownership.
  2. Privatize the copy constructor that takes a const FooAdder &; this prevents the compiler from using the copy constructor on rvalues which would violate your single owner invariant.
  3. Create an auxilary type (say, FooAdderRef) that will be used to transfer ownership between FooAdders. It should contain enough information to transfer ownership.
  4. Add a conversion operator (operator FooAdderRef) to FooAdder that relinquishes ownership in FooAdder and returns a FooAdderRef.
  5. Add a constructor that takes a FooAdderRef and claims ownership from it.

This is identical to what auto_ptr does in case you'd want to look at a real implementation. It prevents arbitrary copying from violating your RAII constraints while allowing you to specify how to transfer ownership from factory functions.

This is also why C++0x has move semantics. Because it's a giant PITA otherwise.

灼疼热情 2024-09-06 23:48:17

朋友模板? (仅使用 gcc 进行测试)

template <class T, class U, class V> struct OtherThing
{
    void init() { }
    void fini() { }
};

template <class T, class U, class V>
class Adder
{
private:

    typedef OtherThing<T, U, V> Thing;
    Thing& thing_;
    int a_;

    Adder( const Adder& );
    Adder& operator=( const Adder& );

    Adder( Thing& thing, int a ) : thing_( thing ), a_( a ) {}

public:

    ~Adder() { thing_.fini(); }
    Adder& foo( T, U ) { return *this; }
    Adder& bar( V ) { return *this; }

    template <class X, class Y, class Z> friend
        Adder<X,Y,Z> make_adder( OtherThing<X,Y,Z>&, int );
};

template <class T, class U, class V>
Adder<T,U,V> make_adder( OtherThing<T,U,V>& t, int a )
{
    t.init();
    return Adder<T,U,V>( t, a );
}

int main()
{
    OtherThing<int, float, char> ot;
    make_adder( ot, 10 ).foo( 1, 10.f ).bar( 'a'
        ).foo( 10, 1 ).foo( 1, 1 ).bar( '0' );
    return 0;
}

A friend template? (tested with gcc only)

template <class T, class U, class V> struct OtherThing
{
    void init() { }
    void fini() { }
};

template <class T, class U, class V>
class Adder
{
private:

    typedef OtherThing<T, U, V> Thing;
    Thing& thing_;
    int a_;

    Adder( const Adder& );
    Adder& operator=( const Adder& );

    Adder( Thing& thing, int a ) : thing_( thing ), a_( a ) {}

public:

    ~Adder() { thing_.fini(); }
    Adder& foo( T, U ) { return *this; }
    Adder& bar( V ) { return *this; }

    template <class X, class Y, class Z> friend
        Adder<X,Y,Z> make_adder( OtherThing<X,Y,Z>&, int );
};

template <class T, class U, class V>
Adder<T,U,V> make_adder( OtherThing<T,U,V>& t, int a )
{
    t.init();
    return Adder<T,U,V>( t, a );
}

int main()
{
    OtherThing<int, float, char> ot;
    make_adder( ot, 10 ).foo( 1, 10.f ).bar( 'a'
        ).foo( 10, 1 ).foo( 1, 1 ).bar( '0' );
    return 0;
}
南风起 2024-09-06 23:48:17

由于 C++03 要求在每个声明中显式地拼写出类型,因此如果没有动态类型,则无法实现这一点,例如让模板从抽象基类继承。

您确实得到了一些巧妙的东西,

AddFoo(myThing, 2) // OK: it's a factory function
  .foo(3, 4)
  .foo(5, 6)
  .bar(7)
  .foo(8, 9); // but object would still get destroyed here

但是在该调用链中编写所有内容会非常痛苦。

C++0x 添加了 auto 类型推导,因此请考虑升级您的编译器,或者如果有的话启用它。 (GCC 上的 -std=c++0x。)

编辑: 如果上面的语法没问题,但你想在一个作用域内有多个链,你可以定义一个 < code>swap 与 void* 操作。

 // no way to have a type-safe container without template specification
 // so use a generic opaque pointer
void *unknown_kinda_foo_handle = NULL;
CreateEmptyFoo(myThing, 2) // OK: it's a factory function
  .foo(3, 4)
  .foo(5, 6)
  .bar(7)
  .foo(8, 9)
  .swap( unknown_kinda_foo_handle ) // keep object, forget its type
  ; // destroy empty object (a la move)

// do some stuff

CreateEmptyFoo(myThing, 2) // recover its type (important! unsafe!)
  .swap( unknown_kinda_foo_handle ) // recover its contents
  .bar( 9 ) // do something
  ; // now it's destroyed properly.

这是非常不安全的,但似乎完全符合您的要求。

编辑:使用默认构造的对象进行交换也是在C++03中模拟move的答案。您需要添加一个默认构造函数,也许还需要添加一个无资源的默认状态,其中析构函数不执行任何操作。

Since C++03 requires explicitly spelling out the type in every declaration, there's no way to accomplish that without dynamic typing, eg having the template inherit from an abstract base class.

You did get something clever with

AddFoo(myThing, 2) // OK: it's a factory function
  .foo(3, 4)
  .foo(5, 6)
  .bar(7)
  .foo(8, 9); // but object would still get destroyed here

but it will be too much of a pain to code everything in that chain of calls.

C++0x adds auto type deduction, so look into upgrading your compiler, or enabling it if you have it. (-std=c++0x on GCC.)

EDIT: If the above syntax is OK but you want to have several chains in a scope, you could define a swap with void* operation.

 // no way to have a type-safe container without template specification
 // so use a generic opaque pointer
void *unknown_kinda_foo_handle = NULL;
CreateEmptyFoo(myThing, 2) // OK: it's a factory function
  .foo(3, 4)
  .foo(5, 6)
  .bar(7)
  .foo(8, 9)
  .swap( unknown_kinda_foo_handle ) // keep object, forget its type
  ; // destroy empty object (a la move)

// do some stuff

CreateEmptyFoo(myThing, 2) // recover its type (important! unsafe!)
  .swap( unknown_kinda_foo_handle ) // recover its contents
  .bar( 9 ) // do something
  ; // now it's destroyed properly.

This is terribly unsafe, but appears to fit your requirements perfectly.

EDIT: swap with a default-constructed object is also the answer to emulating move in C++03. You need to add a default constructor, and perhaps a resource-free default state wherein the destructor does nothing.

梦年海沫深 2024-09-06 23:48:17

这是一种解决方案,但我怀疑还有更好的选择。

FooAdder 提供一个与 std::auto_ptr 的移动语义类似的复制构造函数。为了在没有动态内存分配的情况下完成此操作,复制构造函数可以设置一个标志来指示 dtor 不应该进行总结。像这样:

FooAdder(FooAdder &rhs) // Note: rhs is not const
  : thing_(rhs.thing_)
  , a_(rhs.a_)
  , // etc... lots of other members, annoying.
  , dismiss_(false)
{
  rhs.dismiss_ = true;
}

~FooAdder()
{
  if (!dismiss_)
  {
    // do wrap-up here
  }
}

通过将赋值运算符设置为私有来禁用它可能就足够了——不需要调用它。

Here's one solution, but I suspect there are better options.

Give FooAdder a copy ctor with something similar to std::auto_ptr's move semantics. To do this without dynamic memory allocation, the copy ctor can set a flag to indicate that the dtor shouldn't do the wrap-up. Like this:

FooAdder(FooAdder &rhs) // Note: rhs is not const
  : thing_(rhs.thing_)
  , a_(rhs.a_)
  , // etc... lots of other members, annoying.
  , dismiss_(false)
{
  rhs.dismiss_ = true;
}

~FooAdder()
{
  if (!dismiss_)
  {
    // do wrap-up here
  }
}

It's probably sufficient to disable the assignment operator by making it private -- shouldn't be any need to call it.

梦里南柯 2024-09-06 23:48:17

当我考虑这样的问题时,我通常更愿意首先考虑我希望拥有的接口:

OtherThing<T,U,V> scopedThing = FooAdder(myThing).foo(bla).bar(bla);

我会提出一个非常简单的解决方案:

template <class T, class U, class V>
class OtherThing: boost::noncopyable
{
public:
  OtherThing(); // if you wish

  class Parameters // may be private if FooAdder is friend
  {
  public:
    template<class,class,class> friend class OtherThing;
    Parameters(int,int,int);
    Parameters(const Parameters& rhs);  // proper resource handling
    ~Parameters();                      // proper resource handling

  private:
    Parameters& operator=(const Parameters&); // disabled

    mutable bool dismiss; // Here is the hack
    int p1;
    int p2;
    int p3;
  }; // Parameters

  OtherThing(const Parameters& p);
};

然后:

template <class T, class U, class V>
OtherThing<T,U,V>::Parameters fooAdder(Thing<T,U,V> thing, bla_type, bla_type);

不需要转换运算符等,您可以使用这些运算符来冒险更改不可复制的内容语义,只需创建一个临时结构,您的最终类可从中构造,该结构将用于传递所有参数并更改该结构的语义以获得正确的 RAII。这样,最终类 OtherThing 就没有错误的语义,并且肮脏的内容(dismiss 布尔值)被安全地隐藏在一个永远不应该暴露的临时对象中。

您仍然需要确保正确的异常处理。值得注意的是,这意味着临时 struct 负责资源,只要它不传递给 OtherThing 即可。

我知道这似乎并没有带来太多好处,因为您基本上是要破解 Parameters 而不是 OtherThing,但我强烈建议您考虑这意味着什么:

OtherThing<T,U,V> scopedThing = /**/;
OtherThing<T,U,V>* anotherThing = new OtherThing<T,U,V>(scopedThing);

第二行对于您的尝试性黑客是有效的,因为 scopedThing 可以通过引用以及 const 引用来获取,但它确实会像使用 std::auto_ptr< 那样把事情搞砸。 /代码>。同样,您可以使用 std::vector<其他事物> 并且编译器永远不会抱怨......

When I consider problems like this, I usually prefer to think of the interface I wish to have first:

OtherThing<T,U,V> scopedThing = FooAdder(myThing).foo(bla).bar(bla);

I would propose a very simple solution:

template <class T, class U, class V>
class OtherThing: boost::noncopyable
{
public:
  OtherThing(); // if you wish

  class Parameters // may be private if FooAdder is friend
  {
  public:
    template<class,class,class> friend class OtherThing;
    Parameters(int,int,int);
    Parameters(const Parameters& rhs);  // proper resource handling
    ~Parameters();                      // proper resource handling

  private:
    Parameters& operator=(const Parameters&); // disabled

    mutable bool dismiss; // Here is the hack
    int p1;
    int p2;
    int p3;
  }; // Parameters

  OtherThing(const Parameters& p);
};

And then:

template <class T, class U, class V>
OtherThing<T,U,V>::Parameters fooAdder(Thing<T,U,V> thing, bla_type, bla_type);

There is no need for conversion operators and the like with which you risk to alter the noncopyable semantics, simply create a temporary struct from which your final class is constructible that will be used to pass all the parameters and alter the semantics of this struct for proper RAII. This way the final class OtherThing does not have screwed semantics and the nastiness (dismiss boolean) is safely tucked in a temporary that should never be exposed anyway.

You still need to make sure for proper exception handling. Notably it means that the temporary struct is responsible for the resource as long as it's not passed to OtherThing.

I know it does not seem to bring much to the table since you're basically going to hack Parameters instead of OtherThing, but I urge you to consider what this would mean:

OtherThing<T,U,V> scopedThing = /**/;
OtherThing<T,U,V>* anotherThing = new OtherThing<T,U,V>(scopedThing);

The second line is valid with your tentative hacks, since scopedThing can be taken by reference as well as const reference, but it does screw things up as much as it does with std::auto_ptr. In the same vein, you can then have std::vector< OtherThing<T,U,V> > and the compiler is never going to complain...

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