如何避免将句柄返回到对象内部 - Item 28 Effective C ++

时间:2013-04-15 08:23:16

标签: c++ effective-c++ dangling-pointer

有效C ++的第28项说avoid returning "handles" to object internalsThis 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;
}

我了解将constget_data()一起使用并不能确保安全。如果我使用的是矢量,我可以像上面的示例问题一样复制它,但是因为我想避免这种情况,我想知道如何最好地设计我的课程以避免这种潜在的危险情况?

5 个答案:

答案 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之后)也做了一件“坏事”,因为他对某些东西的破坏性行为并不属于他。