在 C++ 中制作一个安全的缓冲区持有者

Gue*_*OCs 6 c++ memory-leaks memory-management

在某些情况下,我需要char*来回传递缓冲区。我的想法是创建一个对象,该对象可以保存拥有数据的对象,但也可以公开数据char*供某人阅读。由于此对象拥有所有者,因此不会发生内存泄漏,因为当不再需要时,所有者会随对象一起销毁。

我带来了下面的实现,其中我们有一个段错误,我解释了它为什么会发生。事实上,这是我知道如何解决的事情,但这是我的班级引诱我去做的事情。所以我认为我所做的不好,也许在 C++ 中有更好的方法来做到这一点更安全。

请查看我的类,它包含缓冲区所有者并保存指向该缓冲区的原始指针。我曾经GenericObjectHolder是为我持有所有者的东西,而我的Buffer班级没有被这个所有者参数化。

#include <iostream>
#include <string>
#include <memory>
#include <queue>

//The library:
class GenericObjectHolder
{
public:
    GenericObjectHolder()
    {
    }

    virtual ~GenericObjectHolder() {
        
    };
};

template <class T, class Holder = GenericObjectHolder>
class Buffer final
{
public:
    //Ownership WILL be passed to this object
    static Buffer fromOwned(T rawBuffer, size_t size)
    {
        return Buffer(std::make_unique<T>(rawBuffer), size);
    }

    //Creates a buffer from an object that holds the buffer
    //ownership and saves the object too so it's only destructed
    //when this buffer itself is destructed
    static Buffer fromObject(T rawBuffer, size_t size, Holder *holder)
    {
        return Buffer(rawBuffer, std::make_unique<T>(rawBuffer), size, holder);
    }

    //Allocates a new buffer with a size
    static Buffer allocate(size_t size)
    {
        return Buffer(std::make_unique<T>(new T[size]), size);
    }

    ~Buffer()
    {
        if (_holder)
            delete _holder;
    }

    virtual T data()
    {
        return _rawBuffer;
    }

    virtual size_t size() const
    {
        return _size;
    }

    Buffer(T rawBuffer, std::unique_ptr<T> buffer, size_t size)
    {
        _rawBuffer = rawBuffer;
        _buffer = std::move(buffer);
        _size = size;
    }

    Buffer(T rawBuffer, std::unique_ptr<T> buffer, size_t size, Holder *holder)
    {
        _rawBuffer = rawBuffer;
        _buffer = std::move(buffer);
        _size = size;
        _holder = holder;
    }


    Buffer(const Buffer &other)
        : _size(other._size),
          _holder(other._holder),
          _buffer(std::make_unique<T>(*other._buffer)) 
    {
    }


private:
    Holder *_holder;
    T _rawBuffer;
    std::unique_ptr<T> _buffer;
    size_t _size = 0;
};

//Usage:
template <class T>
class MyHolder : public GenericObjectHolder
{
public:
    MyHolder(T t) : t(t)
    {
    }

    ~MyHolder()
    {
    }

private:
    T t;
};

int main()
{
    std::queue<Buffer<const char*, MyHolder<std::string>>> queue;
    std::cout << "begin" << std::endl;
    {
        //This string is going to be deleted, but `MyHolder` will still hold
        //its buffer
        std::string s("hello");
        auto h = new MyHolder<std::string>(s);
    
        auto b = Buffer<const char*, MyHolder<std::string>>::fromObject(s.c_str(),s.size(), h);
        queue.emplace(b);
    }
    {
        auto b = queue.front();
        //We try to print the buffer from a deleted string, segfault
        printf(b.data());
        printf("\n");
    }
    std::cout << "end" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,s字符串被复制到对象持有者内,但在它之后立即被破坏。因此,当我尝试访问buffer拥有的原始缓冲区时,会出现段错误。

当然,我可以简单地将s字符串中的缓冲区复制到对象内的新缓冲区中,但效率很低。

也许有更好的方法来做这样的事情,或者甚至在 C++ 中有一些准备好的东西可以满足我的需要。

PS:字符串只是一个例子。实际上,我可以处理拥有char*缓冲区的任何类型的对象。

现场示例:https : //repl.it/repls/IncredibleHomelySdk

Chr*_*ial 6

你的核心问题是你希望你的 Holder 是可移动的。但是当 Owner 对象移动时,缓冲区对象也可能会移动。这将使您的指针无效。您可以通过以下方式将所有者置于固定的堆位置来避免这种情况unique_ptr

#include <string>
#include <memory>
#include <queue>
#include <functional>

template <class B, class Owner>
class Buffer
{
public:
    Buffer(std::unique_ptr<Owner>&& owner, B buf, size_t size) :
      _owner(std::move(owner)), _buf(std::move(buf)), _size(size)
    {}

    B data() { return _buf; }
    size_t size() { return _size; }

private:
    std::unique_ptr<Owner> _owner;
    B _buf;
    size_t _size;
};

//Allocates a new buffer with a size
template<typename T>
Buffer<T*, T[]> alloc_buffer(size_t size) {
    auto buf = std::make_unique<T[]>(size);
    return {std::move(buf), buf.get(), size};
}
Run Code Online (Sandbox Code Playgroud)

这是一个 repl 链接:https : //repl.it/repls/TemporalFreshApi


如果你想要一个类型擦除的缓冲区,你可以这样做:

template <class B>
class Buffer
{
public:
    virtual ~Buffer() = default;
    B* data() { return _buf; }
    size_t size() { return _size; }

protected:
    Buffer(B* buf, size_t size) : 
      _buf(buf), _size(size) {};
    B* _buf;
    size_t _size;
};


template <class B, class Owner>
class BufferImpl : public Buffer<B>
{
public:
    BufferImpl(std::unique_ptr<Owner>&& owner, B* buf, size_t size) :
      Buffer<B>(buf, size), _owner(std::move(owner))
    {}
    
private:
    std::unique_ptr<Owner> _owner;
};

//Allocates a new buffer with a size
template<typename T>
std::unique_ptr<Buffer<T>> alloc_buffer(size_t size) {
    auto buf = std::make_unique<T[]>(size);
    return std::make_unique<BufferImpl<T, T[]>>(std::move(buf), buf.get(), size);
}
Run Code Online (Sandbox Code Playgroud)

再次,repl 链接:https : //repl.it/repls/YouthfulBoringSoftware#main.cpp


dar*_*une 0

你写了:

在某些情况下,我需要来回传递 char* 缓冲区。

所以我认为我所做的不好,也许有更好的方法在 C++ 中做到这一点,更安全。

目前尚不清楚您的目标是什么,但是当我有这种需要时,我有时会使用std::vector<char>- a std::vector(和std::string) 就是这样:托管缓冲区。调用data()向量将为您提供一个指向缓冲区的原始指针,以传递给遗留接口等,或者出于某种原因您只需要一个您自己管理的缓冲区。提示:使用resize()或 构造函数来分配缓冲区。

所以你看,没有必要std::string在你的例子中存储内部指针。相反,只需data()根据需要致电即可。


看来您关心的是副本和效率。如果您使用支持移动语义的对象 并且使用emplace 系列函数,则至少在中不应进行任何复制。所有/大多数容器也支持移动。