我对C ++很新,所以这可能是一个初学者的问题。它认为做某些我认为相当普遍的事情的“适当”风格。
我正在编写一个函数,在执行其职责时,在堆上分配内存以供调用者使用。我很好奇这个函数的原型应该是什么样子。现在我有:
int f(char** buffer);
要使用它,我会写:
char* data;
int data_length = f(&data);
// ...
delete[] data;
但是,我正在向指针传递一个指针,这让我觉得我可能做错了。
有人想开导我吗?
答案 0 :(得分:6)
在C中,这或多或少是合法的。
在C ++中,函数通常不应该这样做。您应该尝试使用RAII来保证内存不会泄露。
现在你可能会说“它会如何泄漏内存,我只是在那里调用delete[]
!”,但如果在// ...
行引发异常怎么办?
根据功能的确切功能,您可以考虑以下几个选项。一个显而易见的是用向量替换数组:
std::vector<char> f();
std::vector<char> data = f();
int data_length = data.size();
// ...
//delete[] data;
现在我们不再需要显式删除,因为向量是在堆栈上分配的,当析构函数超出范围时会调用它的析构函数。
我应该在回应评论时提到,上述内容暗示了向量的副本,这可能很昂贵。如果f
函数不是太复杂,大多数编译器都会优化该副本,这样就可以了。 (如果没有经常调用该函数,那么开销无关紧要无论如何)。但是如果没有发生这种情况,你可以通过引用将空数组传递给f
函数,并让f
将数据存储在其中,而不是返回一个新的向量。
如果返回副本的性能是不可接受的,另一种选择是完全解耦容器的选择,而是使用迭代器:
// definition of f
template <typename iter>
void f(iter out);
// use of f
std::vector<char> vec;
f(std::back_inserter(vec));
现在可以使用通常的迭代器操作(*out
来引用或写入当前元素,并++out
将迭代器前移到下一个元素) - 更重要的是,所有的标准算法现在可以工作。例如,您可以使用std::copy
将数据复制到迭代器。这是标准库通常选择的方法(即,这是一个好主意;))当函数必须返回一系列数据时。
另一种选择是让你自己的对象负责分配/解除分配:
struct f { // simplified for the sake of example. In the real world, it should be given a proper copy constructor + assignment operator, or they should be made inaccessible to avoid copying the object
f(){
// do whatever the f function was originally meant to do here
size = ???
data = new char[size];
}
~f() { delete[] data; }
int size;
char* data;
};
f data;
int data_length = data.size;
// ...
//delete[] data;
我们再次不再需要显式删除,因为分配是由堆栈上的对象管理的。后者显然是更多的工作,并且存在更多的错误空间,因此如果标准的矢量类(或其他标准库组件)完成工作,则更喜欢它们。此示例仅在您需要根据您的情况定制的内容时才会显示。
C ++中的一般经验法则是“如果你在RAII对象之外写delete
或delete[]
,那你做错了。如果你正在编写{{在RAII对象之外的1}}或`new [],你做错了,除非结果立即传递给智能指针“
答案 1 :(得分:5)
在“正确的”C ++中,您将返回一个包含内部某处内存分配的对象。类似于std :: vector。
答案 2 :(得分:4)
您的函数不应该返回指向某个内存的裸指针。毕竟,指针可以被复制。然后你有所有权问题:谁实际拥有内存并应删除它?您还有一个问题,即裸指针可能指向堆栈上,堆上的单个对象或静态对象。它也可以指向这些地方的阵列。鉴于您返回的只是一个指针,用户应该如何知道?
您应该做的是返回以适当方式管理其资源的对象。 (查看RAII。)假设这种情况下的资源是char
的数组,std::string
或std::vector
似乎是最好的:
int f(std::vector<char>& buffer);
std::vector<char> buffer;
int result = f(buffer);
答案 3 :(得分:3)
为什么不采用与malloc()相同的方式 - void * malloc(size_t numberOfBytes)?这样,字节数是输入参数,分配的块地址是返回值。
UPD: 在评论中你说f()除了分配内存之外基本上执行一些动作。在这种情况下,使用std :: vector是一种更好的方法。
void f( std::vector<char>& buffer )
{
buffer.clear();
// generate data and add it to the vector
}
调用者只会传递一个已分配的向量:
std::vector buffer;
f( buffer );
//f.size() now will return the number of elements to work with
答案 4 :(得分:2)
通过引用传递指针......
int f(char* &buffer)
但是,您可能希望考虑使用引用计数指针(例如boost :: shared_array)来管理内存,如果您刚开始这样做的话。
e.g。
int f(boost::shared_array<char> &buffer)
答案 5 :(得分:1)
使用RAII(资源获取是初始化)设计模式。
http://en.wikipedia.org/wiki/RAII Understanding the meaning of the term and the concept - RAII (Resource Acquisition is Initialization)
答案 6 :(得分:0)
只需返回指针:
char * f() {
return new char[100];
}
话虽如此,您可能不需要像这样混淆显式分配 - 而不是char数组,而是使用std::string
或std::vector<char>
。
答案 7 :(得分:0)
如果f要匹配new[]
,它会起作用,但它不是非常惯用的。
假设f填写数据并且不仅仅是malloc() - 你可以更好地将分配包装为std::vector<char>
void f(std::vector<char> &buffer)
{
// compute length
int len = ...
std::vector<char> data(len);
// fill in data
...
buffer.swap(data);
}
编辑 - 从签名中删除虚假*
答案 8 :(得分:0)
实际上,聪明的做法是将指针放在一个类中。通过这种方式,您可以更好地控制其破坏,并且界面对用户来说更不容易混淆。
class Cookie {
public:
Cookie () : pointer (new char[100]) {};
~Cookie () {
delete[] pointer;
}
private:
char * pointer;
// Prevent copying. Otherwise we have to make these "smart" to prevent
// destruction issues.
Cookie(const Cookie&);
Cookie& operator=(const Cookie&);
};
答案 9 :(得分:0)
如果缓冲区的所有f()
都返回它(及其长度),那么让它返回长度,然后让调用者new
。如果f()
也对缓冲区做了某些事情,那么请按照多语言建议。
当然,对于您想要解决的问题可能有更好的设计,但是我们建议任何事情都需要您提供更多的背景。
答案 10 :(得分:0)
正确的样式可能不是使用char *而是使用std :: vector或std :: string,具体取决于你使用char *的内容。
关于传递要修改的参数而不是传递指针的问题,传递引用。在你的情况下:
int f(char*&);
如果您遵循第一条建议:
int f(std::string&);
或
int f(std::vector<char>&);
答案 11 :(得分:-2)
我猜你正在尝试分配一维数组。如果是这样,则不需要将指针传递给指针。
int f(char* &buffer)
应该足够了。使用场景将是:
char* data;
int data_length = f(data);
// ...
delete[] data;