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*
缓冲区的任何类型的对象。
你的核心问题是你希望你的 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
你写了:
在某些情况下,我需要来回传递 char* 缓冲区。
和
所以我认为我所做的不好,也许有更好的方法在 C++ 中做到这一点,更安全。
目前尚不清楚您的目标是什么,但是当我有这种需要时,我有时会使用std::vector<char>
- a std::vector
(和std::string
) 就是这样:托管缓冲区。调用data()
向量将为您提供一个指向缓冲区的原始指针,以传递给遗留接口等,或者出于某种原因您只需要一个您自己管理的缓冲区。提示:使用resize()
或 构造函数来分配缓冲区。
所以你看,没有必要std::string
在你的例子中存储内部指针。相反,只需data()
根据需要致电即可。
看来您关心的是副本和效率。如果您使用支持移动语义的对象 并且使用emplace 系列函数,则至少在c++17中不应进行任何复制。所有/大多数容器也支持移动。