C++ 为RAII模板类编写对象生成器的更好方法?
我想为一个模板化的RAII类编写一个函数模板——基本上是一个函数模板,它使用参数的类型推断来构造一个对象,这样就不必显式地指定类型 我预见到的问题是,为我处理类型推断的helper函数将按值返回对象,这将(**)导致在复制时过早调用RAII析构函数。也许C++0x移动语义会有所帮助,但这不是我的选择 有人以前见过这个问题,并且有一个好的解决方案吗 这就是我所拥有的:C++ 为RAII模板类编写对象生成器的更好方法?,c++,templates,type-inference,raii,C++,Templates,Type Inference,Raii,我想为一个模板化的RAII类编写一个函数模板——基本上是一个函数模板,它使用参数的类型推断来构造一个对象,这样就不必显式地指定类型 我预见到的问题是,为我处理类型推断的helper函数将按值返回对象,这将(**)导致在复制时过早调用RAII析构函数。也许C++0x移动语义会有所帮助,但这不是我的选择 有人以前见过这个问题,并且有一个好的解决方案吗 这就是我所拥有的: template<typename T, typename U, typename V> class FooAdder
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);
};
FooAdder
构造函数初始化一些内部数据结构。foo
和bar
方法填充这些数据结构。~FooAdder
dtor将东西包装起来,并调用东西
上的一个方法,处理所有的不干净
如果FooAdder
不是一个模板,那么这就可以了。但既然是这样,我就需要输入类型,更像这样:
FooAdder(myThing, 2)
.foo(3, 4)
.foo(5, 6)
.bar(7)
.foo(8, 9);
FooAdder<Abc, Def, Ghi>(myThing, 2) ...
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构造函数关闭它
,因为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上。)
编辑:如果上述语法正常,但希望在一个作用域中有多个链,则可以使用void*
操作定义swap
// 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中模拟移动的答案。您需要添加一个默认构造函数,可能是一个无资源的默认状态,其中析构函数不执行任何操作。您需要一个工作副本构造函数,但在标准中明确允许优化此类副本,这应该是编译器进行的一种常见优化
我想说,这里可能不需要担心移动语义(它可能无论如何都不会工作-请参阅
auto\u ptr\u ref
hackery,这是std::auto\u ptr
所需要的) 这里有一个解决方案,但我怀疑还有更好的选择
为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
}
}
通过将赋值运算符设置为私有的方式禁用它可能就足够了——不需要调用它。朋友模板?(仅使用gcc进行测试)
template struct OtherThing
{
void init(){}
void fini(){}
};
模板
类加法器
{
私人:
类型定义其他事物;
事物&事物;
INTA_;
加法器(常数加法器&);
加法器&运算符=(常数加法器&);
加法器(Thing&Thing,inta):Thing(Thing),a(a){
公众:
~Adder(){thing_u2;.fini();}
加法器&foo(T,U){return*this;}
加法器&bar(V){return*this;}
模板朋友
加法器make_加法器(OtherThing&,int);
};
模板
加法器make_加法器(其他事物&t,int a)
{
t、 init();
返回加法器(t,a);
}
int main()
{
另一件事是ot;
make_加法器(ot,10).foo(1,10.f).bar('a'
).foo(10,1).foo(1,1).bar('0');
返回0;
}
如果你想保证你想做的事情在不使用移动语义的情况下可以正常工作,你需要做auto_ptr
所做的事情,这就是维护所有权状态,并提供一个转换操作符,转换成在auto_ptr
之间转移所有权的类型
就你而言:
FooAdder
中添加一个机制以指示所有权。在FooAdder的
destructor中,仅当cleanup函数拥有所有权时才调用它常量FooAdder&
的复制构造函数;这将防止编译器在右值上使用复制构造函数,这将违反单所有者不变量FooAdderRef
),用于在FooAdders
之间转移所有权。它应该包含足够的信息来转移所有权operator FooAdderRef
)添加到FooAdder
,该运算符放弃FooAdder
中的所有权并返回FooAdderRef
FooAdderRef
,并从中声明所有权auto_ptr
在您想要查看实际实现时所做的相同。它可以防止任意复制违反RAII约束,同时允许您指定如何从工厂函数转移所有权
这也是C++0x具有移动语义的原因。因为它是一个巨大的皮塔,否则, < P>当我考虑这样的问题时,我通常更倾向于考虑我希望拥有的界面:
OtherThing<T,U,V> scopedThing = FooAdder(myThing).foo(bla).bar(bla);
第二行对于您的尝试性破解是有效的,因为scopedThing
可以通过引用和常量引用来获取,但它确实会像使用std::auto\ptr
一样把事情搞砸。同样,您可以使用std::vector
,编译器永远不会抱怨
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);
OtherThing<T,U,V> scopedThing = /**/;
OtherThing<T,U,V>* anotherThing = new OtherThing<T,U,V>(scopedThing);
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);
}