Stroustrup的RAII和演员操作符FILE *()=矛盾?

时间:2014-02-27 12:16:28

标签: c++ pointers raii

我正在阅读Stroustrup的C ++(3ed,1997)以了解他是如何实现RAII的,并且在第365页我发现了这一点:

class File_ptr{
    FILE* p;
public:
    File_ptr(const char* n, const char* a){p = fopen(n, a);}
    File_ptr(FILE* pp) { p = pp; }
    ~File_ptr() {fclose(p);}
    operator FILE* () {return p;}
};

构造函数和析构函数的实现是显而易见的,并且符合RAII惯用法,但我不明白为什么他使用operator FILE* () {return p;}

这将导致以下列方式使用File_ptr

FILE* p = File_ptr("myfile.txt", "r");

关闭p的结果,在这种情况下在语义上是不合适的。此外,如果File_ptr意图用作RAII,则此运算符允许它像示例中那样被滥用。或者我错过了什么?

4 个答案:

答案 0 :(得分:28)

似乎它是不可避免的恶劣价格的舒适。一旦你想要一种方式FILE*可以从你喜欢的RAII类中提取,它就会被误用。它是operator FILE*()还是FILE* getRawPtr()方法,或者其他什么,它可以在temporarty对象上调用,使得结果在返回后无效。

但是,在C ++ 11中,你可以通过禁止对临时对象的这种调用来使这更加安全,如下所示:

operator FILE* () & { return p; }
// Note this -----^

有关Morwenn在评论中提供的工作方式的有用链接:What is "rvalue reference for *this"?

答案 1 :(得分:7)

由于经验的缘故,思维从1997年起已经发生了很大变化,现在的一个主要建议是不要因为这样的问题而使用隐式转换运算符。以前认为有一个隐式转换运算符可以更容易地改进现有代码,但是当函数破坏资源时会导致问题,因为RAII包装类不知道它。

现代约定是提供对底层指针的访问,但是给它一个名称,以便至少它是显式的。它不会捕捉到所有可能的问题,但可以更容易地查找潜在的违规行为。例如std::string c_str()

std::string myString("hello");
callFunctionTakingACharPointer(myString.c_str());
// however...
delete myString.c_str();  // there's no way of preventing this

答案 2 :(得分:4)

  

我不明白为什么他使用运算符FILE *(){return p;}。

运营商的原因是为使用FILE *的API提供访问/兼容性。实现的问题在于它允许客户端代码与您给出的代码类似。

  

这将导致以下列方式使用File_ptr:

FILE* p = File_ptr("myfile.txt", "r");

实际上没有。当可以执行此操作时,类定义不会产生这样的代码。你应该避免编写这种代码(正如你通常需要编写代码来避免生命周期管理问题一样)。

您问题中的RAII示例是设计不佳的示例。可以避免使用转换运算符。

我会用FILE *const File_ptr::get() const访问者替换它。这种改变并没有消除这个问题,但是它更容易在客户端代码中看到你从类中返回一个const指针(即“ClientCode,请不要删除它”)。

答案 3 :(得分:4)

不遵循rule of 3(更不用说5),

因此将函数声明为Bar* createBarFromFile(File_ptr ptr)会发生意外情况(调用此函数后文件将被关闭)

它需要定义一个拷贝构造函数和一个拷贝赋值构造函数。对于规则5,它还需要移动变体

但是如果我被迫使用FILE*作为成员字段,我更喜欢使用std::unique_ptr<FILE, int (__cdecl *)(FILE *)>并在构造函数中传递&fclose