在结构或类中使用智能指针

Posted

技术标签:

【中文标题】在结构或类中使用智能指针【英文标题】:Using smart pointers in a struct or class 【发布时间】:2012-04-13 00:09:53 【问题描述】:

我编写了一个函数,它从文件中加载字节并返回一个包含字节缓冲区和缓冲区长度的 FileData 结构。

我希望缓冲区在被消耗并超出范围后立即被删除。

由于各种转换错误,我无法编译它。另外,我不确定缓冲区是否被正确移动而不是被复制。我不介意 FileData 结构本身被复制,因为它最多可能是 16 个字节。

一般来说,您如何将智能指针用作类/结构字段?那是你会做的事情吗?

我知道,这是一个有点模糊的问题,但由于我在一般意义上对智能指针有一些概念上的困难,我希望这个例子能帮助我朝着正确的方向前进。

这是我目前得到的:

struct FileData

    unique_ptr<char[]> buf;
    unsigned int len;
;

FileData LoadFile(string filename)

    ifstream str;
    str.open(filename, ios::binary);

    str.seekg(0, ios::end);
    auto len = str.tellg();
    str.seekg(0, ios::beg);

    char* buf = new char[len];

    str.read(buf, len);
    str.close();

    FileData d =  unique_ptr<char[]>(buf), len ;

    return d;

编辑:由于有些人对我使用当前代码得到的错误消息感到好奇,所以这里是:

error C2248: 'std::unique_ptr<_Ty>::unique_ptr' : cannot access private member declared in class 'std::unique_ptr<_Ty>'

【问题讨论】:

您的问题是您根本没有提供有关错误消息的任何具体细节。我们到底怎么可能识别它们? @DeadMG 我认为很明显代码存在问题,因为我确实指出我不确定这是使用智能指针和移动语义的正确方法.我希望代码能做的不仅仅是编译;我希望它是正确的和惯用的。尽管如此,我还是用错误消息更新了问题。 你得到的错误是因为你试图复制一个 unique_ptr,你必须使用 std::move。您可以使用 shared_ptr 并声明您自己的释放器,但向量解决方案更简洁。 @pstrjds 我也这么认为。但是,当我这样做时,我得到了一个不同的错误(这就是我不想费心分享错误消息的原因):error C2248: 'std::unique_ptr&lt;_Ty&gt;::unique_ptr' : cannot access private member declared in class 'std::unique_ptr&lt;_Ty&gt;' @ReiMiyasaka - 无论如何,向量解决方案要干净得多,如果您使用的编译器支持 C++11 移动构造函数,那么它不会比复制结构更昂贵。 【参考方案1】:

你的代码很好,除了一个小细节:

struct FileData

    unique_ptr<char[]> buf;
    <del>unsigned int</del> <ins>streamoff</ins> len;
;

它不能为你编译的原因是你的编译器还没有实现特殊移动成员的自动生成。在完全符合 C++11 的编译器中,您的 FileData 的行为就像:

struct FileData

    unique_ptr<char[]> buf;
    streamoff len;

    FileData(FileData&&) = default;
    FileData& operator=(FileData&&) = default;
    FileData(const FileData&) = delete;
    FileData& operator=(const FileData&) = delete;
    ~FileData() = default;
;

默认的移动构造函数只是简单地移动构造每个成员(对于默认的移动赋值也是如此)。

当从LoadFile 返回d 时,会发生一个隐式移动,它将绑定到隐式默认的移动构造函数。

按照其他人的建议使用vector&lt;char&gt;string 也可以。但就 C++11 而言,您的代码没有任何问题。

哦,我可能会这样调整:我希望尽快拥有我的资源:

FileData LoadFile(string filename)

    ifstream str;
    str.open(filename, ios::binary);

    str.seekg(0, ios::end);
    auto len = str.tellg();
    str.seekg(0, ios::beg);

    FileData d = unique_ptr<char[]>(new char[len]), len;

    str.read(d.buf.get(), d.len);
    str.close();

    return d;

如果您需要明确定义 FileData 移动成员,它应该如下所示:

struct FileData

    unique_ptr<char[]> buf;
    streamoff len;

    FileData(FileData&& f)
        : buf(std::move(f.buf)),
          len(f.len)
        
            f.len = 0;
        

    FileData& operator=(FileData&& f)
    
        buf = std::move(f.buf);
        len = f.len;
        f.len = 0;
        return *this;
    
;

哦,这让我想到了另一点。默认移动成员不完全正确,因为它们没有在源中将 len 设置为 0。这是否是错误取决于您的文档。 ~FileData() 不需要 len 来反映缓冲区的长度。但其他客户可能会。如果您将已移动的FileData 定义为没有可靠的len,则默认的移动成员可以,否则它们不是。

【讨论】:

FileData(FileData&amp;&amp;) = default; -- 这是一种适用于某些编译器的速记语法吗?当我尝试自己显式创建移动构造函数时,它抱怨operator= 无法访问。嗯……(顺便说一句,VC++11) @ReiMiyasaka:是的,= default 语法是一种显式请求在 C++98/03 中称为隐式生成的特殊成员的方法。适用于默认 ctor、复制 ctor、复制分配、移动 ctor、移动分配和 dtor。但它可能还没有为你实现(我不熟悉 VC++11)。这些功能是在游戏后期标准化的,因此您的编译器可能还没有它们是可以理解的。 “= delete”语法大致相当于将其声明为私有而不定义它。我不确定您的显式移动 ctor 发生了什么。【参考方案2】:

如果您不介意在返回FileData 时复制std::vector,我可能会使用std::vector 而不是std:::unique_ptr&lt;char[]&gt;

struct FileData 
 
    vector<char> buf; 
; 

FileData LoadFile(string filename) 
 
    ifstream str; 
    str.open(filename, ios::binary); 

    str.seekg(0, ios::end); 
    auto len = str.tellg(); 
    str.seekg(0, ios::beg); 

    FileData d; 
    d.buf.resize(len); 

    str.read(&(d.buf)[0], len); 
    str.close(); 

    return d; 
 

或者,为了避免复制,调用者可以传入FileData 作为函数参数而不是返回值:

struct FileData 
 
    vector<char> buf; 
; 

void LoadFile(string filename, FileData &data) 
 
    ifstream str; 
    str.open(filename, ios::binary); 

    str.seekg(0, ios::end); 
    auto len = str.tellg(); 
    str.seekg(0, ios::beg); 

    data.buf.resize(len); 

    str.read(&(data.buf)[0], len); 
    str.close(); 
 

【讨论】:

这不会导致文件数据“数组”本身被复制,还是vector实现了C++11的移动语义?不想争论,只是好奇。 在那种情况下,我什至不需要 FileData 结构,这样就回答了我的一半问题。谢谢。我认为调整矢量大小的性能不会太差? @Rei:在给定的示例中它不会调整大小。因为它最初是空的,它更像是 "size" :P 并且不会比自己分配内存慢。 回答了我自己的好奇心 - cprogramming.com/c++11/…【参考方案3】:

如何使用 std::string 作为缓冲区。它有你想要的所有行为:

引用计数而不是复制 一旦超出范围就会消失 保存任意数量的任意字节

人们会对此投反对票,因为它不是字符串的最初预期用途;可能派生一个类(或包装它)并将其称为“缓冲区”

【讨论】:

我认为这可行,但同样,它并不能帮助我弄清楚在其他类似情况下如何使用智能指针。 我不知道 unique_ptr(我使用 boost)但是 unique_ptr 在我看来是错误的,通常数组 ptr 有自己的类。我会有 uniq_ptr。从根本上说它应该工作 std::string 何时成为引用计数?还是在不同的编译器中是特定于实现的? 规范什么也没说(我不认为),但所有的实现都是引用计数的(它做起来很简单,效率更高) @pm100:旧答案,所以我不会 -1,但在您撰写本文时这是错误的(MSVC 不做 COW,我认为它从来没有),现在GCC 也没有。新标准禁止这样做。

以上是关于在结构或类中使用智能指针的主要内容,如果未能解决你的问题,请参考以下文章

智能指针 与 oc中的指针

-> 在智能指针中的使用

C++智能指针类模板

使用智能指针释放内存

C++ 结构体与类指针

智能指针如何影响 5 规则?