4

我想为模板化的 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方法填充这些数据结构。~FooAdderdtor 将事情包装起来并调用 on 方法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。大多数编译器都这样做,但这不是必需的,可以在 gcc 中使用-fno-elide-constructors.

4

7 回答 7

3

这似乎很容易。提问者自己提出了一个很好的解决方案,但他可以只使用带有 const-reference 参数的普通复制构造函数。以下是我在评论中提出的建议:

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);
}

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

于 2010-04-30T18:52:34.997 回答
1

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

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

于 2010-04-29T19:12:33.760 回答
1

如果你想保证你想要做的事情在不使用移动语义的情况下可以工作,你需要做的事情auto_ptr是维护所有权状态,并为在auto_ptrs.

在你的情况下:

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

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

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

于 2010-04-29T22:58:47.753 回答
0

由于 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类型推导,所以考虑升级你的编译器,或者如果你有的话启用它。(-std=c++0x在 GCC 上。)

编辑:如果上面的语法没问题,但你想在一个范围内有几个链,你可以定义一个swapwithvoid*操作。

 // 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.

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

编辑swap使用默认构造的对象也是move在 C++03 中模拟的答案。您需要添加一个默认构造函数,也许还有一个无资源的默认状态,其中析构函数什么都不做。

于 2010-04-29T18:53:34.357 回答
0

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

给出FooAdder一个类似于std::auto_ptr' 移动语义的复制 ctor。要在没有动态内存分配的情况下执行此操作,复制 ctor 可以设置一个标志来指示 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
  }
}

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

于 2010-04-29T19:45:09.280 回答
0

朋友模板?(仅使用 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;
}
于 2010-04-29T20:20:58.130 回答
0

当我考虑这样的问题时,我通常更喜欢首先想到我希望拥有的界面:

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而不是hack OtherThing,但我敦促您考虑这意味着什么:

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

第二行对您的试探性技巧有效,因为scopedThing可以通过引用和 const 引用来获取,但它确实与std::auto_ptr. 同样,您可以拥有std::vector< OtherThing<T,U,V> >并且编译器永远不会抱怨......

于 2010-04-30T06:48:07.300 回答