C++11 移动构造函数和常量成员变量
我喜欢常量成员变量的概念,尤其是当我将C函数包装到类中时。构造函数接受在整个对象生命周期内保持有效的资源句柄(例如文件描述符),析构函数最终将其关闭。(这就是RAII背后的想法,对吗?) 但是使用C++0x move构造函数时,我遇到了一个问题。由于析构函数也在“unload”对象上调用,因此我需要防止清理资源句柄。由于成员变量是const,我无法指定值-1或无效的_句柄(或等效值),以指示析构函数不应执行任何操作 如果一个对象的状态被移动到另一个对象,是否有一种方法不调用析构函数 例如:C++11 移动构造函数和常量成员变量,c++11,raii,move-constructor,C++11,Raii,Move Constructor,我喜欢常量成员变量的概念,尤其是当我将C函数包装到类中时。构造函数接受在整个对象生命周期内保持有效的资源句柄(例如文件描述符),析构函数最终将其关闭。(这就是RAII背后的想法,对吗?) 但是使用C++0x move构造函数时,我遇到了一个问题。由于析构函数也在“unload”对象上调用,因此我需要防止清理资源句柄。由于成员变量是const,我无法指定值-1或无效的_句柄(或等效值),以指示析构函数不应执行任何操作 如果一个对象的状态被移动到另一个对象,是否有一种方法不调用析构函数 例如: cl
class File
{
public:
// Kind of "named constructor" or "static factory method"
static File open(const char *fileName, const char *modes)
{
FILE *handle = fopen(fileName, modes);
return File(handle);
}
private:
FILE * const handle;
public:
File(FILE *handle) : handle(handle)
{
}
~File()
{
fclose(handle);
}
File(File &&other) : handle(other.handle)
{
// The compiler should not call the destructor of the "other"
// object.
}
File(const File &other) = delete;
File &operator =(const File &other) = delete;
};
不,这是不可能的。我建议,如果您真的附加到
handle
变量为const,那么您应该有一个non-const flag成员变量,该变量指示销毁是否应该执行任何操作。实现移动构造函数的典型方法是将正在移动的实例的成员归零或以其他方式使其无效(有关简单示例,请参见)因此,我想说的是不要在这里使用const
,因为它与移动语义的目标不兼容。这就是为什么不应该声明所述成员变量const
const
成员变量通常没有任何用途。如果不希望用户对文件*
进行变异,那么就不要提供它们如果您想阻止自己意外地对其进行变异,请标记您的函数const
。但是,不要让成员变量本身const
——因为这样,当您开始使用移动或复制语义时,您会感到很有趣。引用计数是解决问题的标准方法考虑将引用计数添加到您的类中;手动或使用现有的工具如Boost SyrdyPtR.,实际上,我今天也遇到了这个问题。不愿意接受“不能做”和“使用SysDypPTR/引用计数”,Google更多,我提出了这个基类:
class Resource
{
private:
mutable bool m_mine;
protected:
Resource()
: m_mine( true )
{
}
Resource(const Resource&) = delete;
void operator=(const Resource&) = delete;
Resource(const Resource&& other)
: m_mine( other.m_mine )
{
other.m_mine = false;
}
bool isMine() const
{
return m_mine;
}
};
所有方法和构造函数都受保护,您需要从中继承才能使用。请注意可变字段:这意味着后代可以是类中的常量成员。例如
class A : protected Resource
{
private:
const int m_i;
public:
A()
: m_i( 0 )
{
}
A( const int i )
: m_i( i )
{
}
A(const A&& a)
: Resource( std::move( a ) )
, m_i ( std::move( a.m_i ) ) // this is a move iff member has const move constructor, copy otherwise
{
}
~A()
{
if ( isMine() )
{
// Free up resources. Executed only for non-moved objects
cout << "A destructed" << endl;
}
}
};
请注意,B的字段也可以是常量。我们的移动构造函数是常量。如果您的类型具有适当的移动构造函数,则任何堆分配的内存都可以在对象之间共享。常量成员变量与引用一样有用。通常,您希望保留一个值,并且知道您不会更改它,这可能会允许一些优化将方法标记为const将只在我没有一些可变变量的情况下才有效。根据我的经验,它实际上不允许任何有用的编译器优化。而且它阻止了许多非常有用的语义。完全正确!我遇到了乐趣,因为我将基础类的一个成员声明为const成员。const字段是useful,因为如果您忘记使用构造函数设置它们,编译器可能会抱怨。
const
与避免全局变量或试图缩小范围的目的相同:减少心理负荷。当您知道该字段在整个对象生命周期中都是相同的时,您不需要每次在调试器或重构时都查找它一个系统语言生锈在这方面采取了更好的方法:它具有所有不变的,并且能够改变一个变量,你必须明确地声明它是“代码”MUT 。C++中只允许<代码> MUT < /C> >的一种悲哀,因此你不能通过声明一大堆const别名来模仿这种行为:CYY答案肯定是A。很好。但是与C++0x相关,我不喜欢析构函数必须检查是否真的发生了析构函数的样式。他们不应该假设对象已经完全启动并运行,现在是毁灭点吗?@mazatwork:好吧,这样想。假设你有一个复杂的对象,它可能处于几种不同的状态,并且每个状态都是相同的需要一组不同的析构函数。例如,有一个缓存可能被初始化,也可能没有初始化,或者有一个数据库连接可能需要关闭,也可能不需要关闭。如果不关闭析构函数中未打开的数据库连接,您是否真的在销毁?当然不是。这基本上是一样的。您仍然是l销毁,只是对象所处的状态不能满足很多工作。为什么不让move构造函数进行一些清理(如果确实需要的话)因此,析构函数只剩下真正的销毁。在我看来,这更合适。因为我们谈论同一个对象,所以双重销毁可能不合理。我尽量避免使用RAII和DI等技术来处理复杂对象。@matzawork:这就是你的移动构造函数所做的。清理你的对象,因此析构函数没有任何工作要做。我不理解这个问题。这是所提出问题的一个正交主题(处理文件的移动)。您建议的是使shared_ptr
s。这可能是合适的,但可能不是,而且它肯定会涉及更大的设计更改。您可以实际使用const
,编译器仍然会生成移动构造函数(当然,如果您满意的话)。下面是一个示例作为证明:(忽略代码板旧编译器错误..)
struct B
{
const A m_a;
const X m_x;
B(const A&& a, const X& x) // implement this way only if X has copy constructor; otherwise do for 'x' like we do for 'a'
: m_a( std::move( a ) )
, m_x( x )
{
}
B( const B&& b )
: m_a( std::move( b.m_a ) )
, m_x( std::move( b.m_x ) ) // this is a move iff X has move constructor, copy otherwise
{
}
~B()
{
cout << "B destructed" << endl;
}
};