2011-06-11 132 views
16

我喜欢const成员变量的想法,尤其是当我将C函数包装到类中时。构造函数需要一个资源句柄(例如文件描述符),该句柄在整个对象生命周期内保持有效,并且析构函数最终关闭它。 (这是背后的想法,对吧?)移动构造函数和const成员变量

但与C++ 0x移动构造函数我遇到了问题。由于析构函数也被称为“未加载”的对象,我需要防止清理资源句柄。由于成员变量是const我没有办法分配值-1或INVALID_HANDLE(或等价值),以指示析构函数,它不应该做任何事情。

如果一个对象的状态被移动到另一个对象时,有没有方法可以调用析构函数?

例子:

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

回答

7

没有,有没有办法做到这一点。我建议如果你真的接受了handle变量为const,那么你应该有一个非const标志成员变量,它指示了销毁是否应该做任何事情。

+1

你的答案肯定是一种方式。但是与C++ 0x有关,我不喜欢析构函数必须检查是否应该真正发生解析的样式。他们是不是应该认为这个物体已经完全投入运行,现在已经成为毁灭的一点? – mazatwork 2011-06-11 18:28:16

+1

@mazatwork:好吧,想想这样。假设你有一个复杂的对象,可能处于几种不同的状态,每个状态都需要一组不同的析构函数。例如,就像有一个缓存可能会或可能不会被初始化,或者可能需要或不需要关闭的数据库连接。当你没有关闭未在析构函数中打开的数据库连接时,你是不是“真的在破坏”?当然不是。这基本上是一回事。你仍然在摧毁,只是对象所在的状态不能满足很多工作。 – Omnifarious 2011-06-11 19:00:37

+0

为什么不让移动构造函数做一些清理(如果它实际上是必要的),所以析构函数留下了真正的破坏。在我看来,这会更好。因为我们谈到同一个对象,双重破坏可能是不合理的。你用复杂对象的例子是我尝试避免使用像RAII和DI这样的技术的一个例子。 – mazatwork 2011-06-11 19:56:21

3

实现移动构造函数的典型方法是将被移动实例的成员置零或以其他方式使其失效(有关简单示例,请参见MSDN)。因此,我认为在这里不要使用const,因为它与移动语义的目标不兼容。

+0

那么,你实际上可以使用'const',编译器仍然会生成移动构造函数(当然,如果满足[所有需求](https://stackoverflow.com/a/8285499))。下面是一个例子作为证明:[codepad.org/Xh4va2eR](http://codepad.org/Xh4va2eR)(忽略键盘旧的编译器错误..) – 2017-09-04 13:59:13

13

这就是为什么你不应该声明所述成员变量constconst成员变量通常不起任何作用。如果你不希望用户改变FILE*,那么不要为它们提供这样做的功能,并且如果你想阻止你偶然改变它,那么标记你的函数const。但是,请勿自行创建成员变量const - 因为那时您在开始使用移动或复制语义时遇到乐趣

+7

Const成员变量是有用的像引用。通常你想保持一个值,你知道你不会改变它,这可能允许编译器进行一些优化。 只有当我没有一些可变变量时,才将这些方法标记为const。 – mazatwork 2011-06-11 17:53:18

+3

根据我的经验,它并没有真正允许任何有用的编译器优化。它阻止了许多非常有用的语义。 – Puppy 2013-01-14 23:01:09

+0

确实如此!我遇到了** fun **,因为我将一个基础类的成员声明为一个const成员。 – user8385554 2017-08-18 06:18:28

-1

引用计数是解决您的问题的标准方法。考虑给你的班级添加引用计数;手动或使用现有的工具,如boost shared_ptr。

+2

这是一个正在提出的问题(正在处理“文件”的移动)的正交主题。你提出的建议是'shared_ptr 's。可能是合适的,但可能不合适,而且肯定会涉及更大的设计更改 – 2014-04-02 18:05:18

0

其实我今天也遇到过这个问题。不愿意接受“不能做” &“使用的shared_ptr /引用计数”,谷歌上搜索更多的,我想出了这个基类:

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

现在A的字段可以是const。请注意,我已经继承了protected,所以用户不会意外地将A转换为资源(或者非常乐意去破解它),但是A仍然不是最终的,因此您仍然可以继承它(从Resource继承的有效理由是例如具有分开的读取和读写访问)。这是极少数情况下保护继承并不意味着您的设计有问题的情况之一;然而,如果你觉得难以理解,你可能只是使用公有继承。

然后,假设你有一个struct X

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

需要注意的是B的领域也可以是常量。我们的移动构造函数是const。鉴于你的类型有适当的移动构造函数,任何堆分配的内存可以在对象之间共享。