5

无法弄清楚如何准确地表达问题,所以这里有一个例子:

给定这个函数原型:

void Foo(myClass* bar);

我想防止这种用法:

Foo(new myClass());

而是需要一个先前创建的对象:

myClass* bar = NULL;
bar = new myClass();
Foo(bar);

或者

myClass bar;
Foo(&bar);

谢谢。


编辑

这是一个明确的例子:


void Mouse::SetImage(BITMAP* image, int focusX, int focusY) {
    if(_image) {
        set_mouse_sprite(NULL);
        set_mouse_sprite_focus(0, 0);
        show_mouse(NULL);
        destroy_bitmap(_image);
        _image = NULL;
    }
    if(image) {
        _image = create_bitmap(image->w, image->h);
        clear_bitmap(_image);
        blit(image, _image, 0, 0, 0, 0, image->w, image->h);
    }
    if(image == NULL) {
        focusX = 0;
        focusY = 0;
    }
    _focusX = focusX;
    _focusY = focusY;
    _dirtyImage = true;
}

用户传入的任何图像都会被复制到对象的图像中。

如果我在复制后解除分配传入的图像并且图像在程序的其他地方使用,它将导致程序因访问冲突而崩溃。

如果他们在线分配存储而我没有释放它,则会发生内存泄漏。如果他们在运行程序的过程中多次调用 SetImage 方法,则问题会更加复杂。

关于使用替代库或 Allegro 库本身的评论将被忽略,我已经知道这很可怕。我别无选择。

4

5 回答 5

19

您的设计需要做出选择。要么取得所有权并将其删除,要么不取得所有权。无论哪种方式,都取决于用户知道如何使用您的功能。他们要么需要知道你的函数会破坏图像(并且可能会根据需要传递他们自己的副本),要么他们需要足够聪明来管理自己的资源。

通常,您不想仅仅为了删除它而窃取所有权。所以我不会删除任何东西。如果有人傻到失去删除他们传递的图像的能力,那不是这个功能问题。换句话说,您应该尝试防御墨菲,但忘记防御马基雅维利。

也就是说,原始指针的使用很糟糕!糟糕的 C++ 代码以手动资源管理和资源问题为标志。您应该在图像周围有一个包装器,它将删除析构函数中的图像。这样,即使抛出异常,您也永远不会泄漏。为它提供一种reset()丢弃旧图像资源并获取新图像资源的方法。

听起来您想要共享所有权,因此您需要一个引用计数资源包装器。然后问题就解决了:如果有人进行“内联”分配,它将被放入共享指针中,然后在完成后自动删除。(更好的是有一个explicit构造函数,所以有人必须知道他们将共享资源。)

这是在一个名为 的智能指针中完成的shared_ptrBoost有一个,TR1 有一个,C++0x 有一个。只需给它一个自定义删除(释放图像),您就再也不用担心资源管理了。

这应该使用所有资源来完成。这里的概念是Scoped-bound Resource Management (SBRM);通过利用自动(堆栈)变量的生命周期规则来自动管理资源。它被称为 alos,因为它是原始但更丑陋的名称Resource-Acquisition Is Initialization (RAII)。对这个领域做一些研究,你会发现你的代码更简单、更干净。


如果不更改参数的类型,则无法完成。您可以将其更改为:

void Foo(myClass*& bar);

因为非常量引用只能绑定到左值:

void foo(int*&);

int main(void)
{
    int *i = 0;
    int j;

    foo(i); // well-formed
    foo(&j); // ill-formed
    foo(new int); // ill-formed
}

但是,这不允许获取左值的地址。你当然可以做简单的事情:

int main(void)
{
    int j;
    int* pj = &j;
    foo(pj); // well-formed
}

它有效。但我不知道你为什么要这样做。


上述解决方案将允许您修改参数(因为它是参考)。如果你想在函数中强制执行 const,你可以创建一个这样的实用程序:

template <typename T>
class require_lvalue
{
public:
    require_lvalue(T& pX) :
    mX(pX)
    {}

    const T& get(void) const
    {
        return mX;
    }

    operator const T&(void) const
    {
        return get();
    }

private:
    // non-copy-assignable
    require_lvalue& operator=(const require_lvalue&);

    const T& mX;
};

void foo(require_lvalue<int*>);

相同的结果,除了你在函数中有一个常量引用。


请注意,MSVC 有一个错误,并接受这一点:

foo(new int);

在这两种情况下,即使它不应该。(但是,它不接受new int()。)

于 2010-08-15T21:02:37.207 回答
4

不可能有这样的用法区别。在所有情况下,它都是一个有效参数。我真的不明白你为什么需要这个......

于 2010-08-15T20:42:52.863 回答
2

所以不要使用指针......使用(左值)引用:

void Foo(myClass& bar);
于 2010-08-15T21:01:48.710 回答
2

这不能解决你的任务吗?但无论如何,对于这种情况,我还是推荐 std::auto_ptr 之类的东西。

#include <iostream>

void test(int *& ptr)
{
    std::cout << *ptr << std::endl;
}

int main()
{
/* NEXT LINE WILL FAIL */
//  test(new int(5));

    int *b = new int(5);
    test(b);
    delete b;

    return 0;
}
于 2010-08-15T21:16:25.843 回答
-1

C 或 C++ 不会让您确定分配内存的位置,即进入函数参数的位置。如果您想要更好的安全性,请使用 .NET 进行编程。

如果你想让它更安全,不仅仅是完全改变你的函数签名来接受自动指针。这样,语义就变得非常清晰,不应该混淆谁或什么拥有记忆。

于 2010-08-16T02:42:30.790 回答