有效C ++的第28项说avoid returning "handles" to object internals
。 This question展示了如何通过考虑封装来设计代码以完全实现这一目的,以避免意外暴露您的类的内部。
我的示例涉及数据数组,因为内存是一个问题,我想避免使用std::vector
(以及Boost库)。
在这里使用数组是我的代码的一个非常简化的版本:
class Foo {
public:
Foo(int size) : data_(new int[size]) {
// Populate with data just for the example
for(int i = 0; i < size; i++) {
data_[i] = i;
}
}
// I know that this isn't good practice
int* const get_data() const {
return data_;
}
~Foo() {
delete[] data_;
}
private:
int* data_;
};
int main(void) {
Foo* foo = new Foo(10);
int* const data = foo->get_data();
delete foo;
// data now dangles which is bad!
return 0;
}
我了解将const
与get_data()
一起使用并不能确保安全。如果我使用的是矢量,我可以像上面的示例问题一样复制它,但是因为我想避免这种情况,我想知道如何最好地设计我的课程以避免这种潜在的危险情况?
答案 0 :(得分:3)
编程和软件设计是一个不断“选择一件事而不是另一件事”。如果您真的无法负担使用std::vector
,并且您需要使用您的类将数据公开给客户端代码,那么就这样吧。记录为什么会这样,并确保随着时间的推移对其进行审核 - 可能是当你的新型硬件具有8MB RAM而不是2MB时,可能会使用vector
所需的额外4个字节在你的普通指针上。
或者写一个访问者函数:
int& Foo::operator[](int pos)
{
// Add checks here to ensure `pos` is in range?
return data[pos];
}
现在你根本不必返回指针......
答案 1 :(得分:1)
您的情况中的一种方法是使用智能指针。我更喜欢shared_ptr。以下是修改课程的方法,以避免出现问题:
class Foo {
public:
Foo(int size) : data_(new int[size]) {
// Populate with data just for the example
for(int i = 0; i < size; i++) {
data_[i] = i;
}
}
// I know that this isn't good practice
std::shared_ptr<int> const get_data() const {
return data_;
}
~Foo() {
//delete[] data_;
}
private:
std::shared_ptr<int> data_;
};
int main(void) {
Foo* foo = new Foo(10);
std::shared_ptr<int> data = foo->get_data();
delete foo;
// data still good )) // --now dangles which is bad!--
return 0;
}
答案 2 :(得分:0)
如果你真的希望避免使用std :: vector,你可以实现类似于某些winapi函数使用的方法。
size_t get_data(int* data_ptr, size_t& buffer_size)
{
if(buffer_size < sizeof (data_)
{
buffer_size = sizeof(data);
return 0;
}
//You could you memcpy for ints here
for(int i = 0; i < size; i++) {
data_ptr[i] = i;
}
return sizof(data_);
}
因此要求调用者提供存储器来存储数据并关心该存储器的分配 通过它应该重新发明什么std :: vector已经做了更少的安全性。 此外,您应该检查Rule of three以避免在您开始更广泛地使用课程时出现问题。
答案 3 :(得分:0)
有很多情况下返回指针是完全可以接受的,即使数组实际上包含在std::vector
中;例如使用外部API以及复制数据时不受欢迎。
我没有看到危险,因为你没有为猴子编程。班上的用户应该知道。但我添加了查询数据大小的函数。那是为了安全。
extern "C"{
void foobar( const int *data, unsigned int size );
}
class Foo {
public:
Foo(unsigned int size) : size_(size), data_(new int[size]) {
// Populate with data just for the example
for(unsigned int i = 0; i < size; i++) {
data_[i] = i;
}
}
unsigned int get_size() const{return size_;}
int* const get_data() const {
return data_;
}
~Foo() {
delete[] data_;
}
private:
int* data_;
unsigned int size_;
};
int main(void) {
Foo* foo = new Foo(10);
int* const data = foo->get_data();
foobar( data, foo->get_size() );
delete foo;
return 0;
}
答案 4 :(得分:0)
考虑到标准库在get_native_handle()
这样的类上有std::thread
我想知道标准库或EC ++之间是谁错了!
有效的C ++是一套指导原则,您无法在任何上下文中应用它们。这是一本“道德”的书。不要将其视为“合法”。如果您需要公开数据,请执行此操作。
毕竟谁删除了一个指向内部数据的指针(在通过getter之后)也做了一件“坏事”,因为他对某些东西的破坏性行为并不属于他。