C++ “智能指针”自动转换为裸指针但无法显式删除的模板

发布于 2024-09-11 04:30:28 字数 566 浏览 8 评论 0原文

我正在一个非常大的遗留 C++ 代码库中工作,该代码库将保持匿名。作为遗留代码库,它在各处传递原始指针。但我们正在逐渐尝试使其现代化,因此也有一些智能指针模板。这些智能指针(与 Boost 的scoped_ptr 不同)具有到原始指针的隐式转换,因此您可以将其中之一传递到采用原始指针的例程中,而无需编写 .get()。这样做的一个很大的缺点是,您也可能不小心在删除语句中使用了一个,然后就会出现双重释放错误,这可能是一个真正难以追踪的痛苦。

有没有办法修改模板,使其仍然具有到原始指针的隐式转换,但如果在删除语句中使用会导致编译错误?像这样:

#include <my_scoped_ptr>

struct A {};
extern void f(A*);

struct B
{
    scoped_ptr<A> a;

    B();
    ~B();
};

B::B()
    : a(new A)
{
    f(a); // this should compile
}

B::~B()
{
    delete a; // this should NOT compile
}

I am working in a very large legacy C++ code base which shall remain nameless. Being a legacy code base, it passes raw pointers around all over the place. But we are gradually trying to modernize it and so there are some smart pointer templates as well. These smart pointers (unlike, say, Boost's scoped_ptr) have an implicit conversion to the raw pointer, so that you can pass one of them into a routine that takes a raw pointer without having to write .get(). A big downside of this is that you can also accidentally use one in a delete statement, and then you have a double free bug, which can be a real pain to track down.

Is there a way to modify the template so that it still has the implicit conversion to the raw pointer, but causes a compile error if used in a delete statement? Like this:

#include <my_scoped_ptr>

struct A {};
extern void f(A*);

struct B
{
    scoped_ptr<A> a;

    B();
    ~B();
};

B::B()
    : a(new A)
{
    f(a); // this should compile
}

B::~B()
{
    delete a; // this should NOT compile
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

奈何桥上唱咆哮 2024-09-18 04:30:28

标准说

操作数应具有指针类型,或具有到指针类型的单个转换函数(12.3.2)的类类型。如果操作数是类类型,则通过调用上述转换函数将操作数转换为指针类型,并在本节的其余部分中使用转换后的操作数代替原始操作数。

您可以通过声明转换函数的 const 版本来 (ab) 使用缺少重载解析的情况。在符合标准的编译器上,使用 delete 就足以使其不再工作:

struct A {
  operator int*() { return 0; }
  operator int*() const { return 0; }
};

int main() {
  A a;
  int *p = a; // works
  delete a; // doesn't work
}

结果如下 在

[js@HOST2 cpp]$ clang++ main1.cpp
main1.cpp:9:3: error: ambiguous conversion of delete expression of type 'A' to a pointer
  delete a; // doesn't work
  ^      ~
main1.cpp:2:3: note: candidate function            
  operator int*() { return 0; }
  ^
main1.cpp:3:3: note: candidate function             
  operator int*() const { return 0; }
  ^
1 error generated.

这方面不太符合标准的编译器(EDG/Comeau、GCC)上,您可以将转换函数设为模板。 delete 不需要特定类型,因此这可以工作:

template<typename T>
operator T*() { return /* ... */ }

但是,这有一个缺点,您的智能指针现在可以转换为任何指针 -类型。尽管实际的转换仍然经过类型检查,但这不会预先排除转换,而是会在稍后给出编译时错误。遗憾的是,SFINAE 似乎无法使用 C++03 中的转换函数:) 另一种方法是从其他函数返回私有嵌套类型指针

struct A {
  operator int*() { return 0; }

private:
  struct nested { };
  operator nested*() { return 0; }
};

现在唯一的问题是转换为 void*,在这种情况下,两个转换函数同样可行。 @Luther 建议的一个解决方法是从另一个转换函数返回一个函数指针类型,它适用于 GCC 和 Comeau,并摆脱了 void* 问题,同时在没有其他问题的情况下通常的转换路径,与模板解决方案不同,

struct A {
  operator int*() { return 0; }

private:
  typedef void fty();
  operator fty*() { return 0; }
};

但请注意,只有不符合标准的编译器才需要这些解决方法。

The Standard says

The operand shall have a pointer type, or a class type having a single conversion function (12.3.2) to a pointer type. If the operand has a class type, the operand is converted to a pointer type by calling the above-mentioned conversion function, and the converted operand is used in place of the original operand for the remainder of this section.

You can (ab)-use the absence of overload resolution by declaring a const version of the conversion function. On a conforming compiler that's enough to make it not work anymore with delete:

struct A {
  operator int*() { return 0; }
  operator int*() const { return 0; }
};

int main() {
  A a;
  int *p = a; // works
  delete a; // doesn't work
}

Results in the following

[js@HOST2 cpp]$ clang++ main1.cpp
main1.cpp:9:3: error: ambiguous conversion of delete expression of type 'A' to a pointer
  delete a; // doesn't work
  ^      ~
main1.cpp:2:3: note: candidate function            
  operator int*() { return 0; }
  ^
main1.cpp:3:3: note: candidate function             
  operator int*() const { return 0; }
  ^
1 error generated.

On compilers that are less conforming in that regard (EDG/Comeau, GCC) you can make the conversion function a template. delete does not expect a particular type, so this would work:

template<typename T>
operator T*() { return /* ... */ }

However, this has the downside that your smartpointer is now convertible to any pointer-type. Although the actual conversion is still typechecked, but this won't rule out conversions up-front but rather give a compile time error much later. Sadly, SFINAE does not seem to be possible with conversion functions in C++03 :) A different way is to return a private nested type pointer from the other function

struct A {
  operator int*() { return 0; }

private:
  struct nested { };
  operator nested*() { return 0; }
};

The only problem now is with a conversion to void*, in which case both conversion functions are equally viable. A work-around suggested by @Luther is to return a function pointer type from the other conversion function, which works with both GCC and Comeau and gets rid of the void* problem while having no other problems on the usual conversion paths, unlike the template solution

struct A {
  operator int*() { return 0; }

private:
  typedef void fty();
  operator fty*() { return 0; }
};

Notice that these workarounds are only needed for compilers that are not conforming, though.

塔塔猫 2024-09-18 04:30:28

没有办法可以阻止一个而不阻止另一个。任何可以将其隐式转换为函数调用指针的地方,都可以将其隐式转换为删除表达式。

最好的办法是删除转换功能。您的情况正是用户定义的转换运算符很危险且不应经常使用的原因。


我错了。:(

There isn't a way to stop one and not the other. Anywhere it can be implicitly converted to a pointer for a function call, it can be implicitly converted for a delete expression.

Your best bet is to remove the conversion function. Your situation is exactly why user-defined conversion operators are dangerous and shouldn't be used often.


I'm wrong. :(

冬天旳寂寞 2024-09-18 04:30:28

您可以使用 Boost 提出的技术,但我担心的是,您允许从智能指针到原始指针的隐式转换,这通常是不受欢迎的。此外,用户可以在 -> 运算符获得的指针上调用 delete ,因此您实际上无法阻止一个顽固的白痴绕过您所采用的任何机制跟上。

您确实应该只实现 get() 方法,而不是提供 operator T*() ,这样至少对 delete smartptr 的调用不会编译。非白痴应该能够弄清楚这可能是行不通的。

是的,输入 LegacyFunc(smartptr.get()) 比输入 LegacyFunc(smartptr) 需要更多工作,但前者是首选,因为它使其显式化并防止意外转换防止发生,例如delete smartptr

如果你有这样的函数怎么办:

 void LegacyOwnPointer(SomeType* ptr);

函数将指针存储在某个地方?这会搞砸智能指针,因为现在它不知道其他东西正在拥有原始指针。

不管怎样,你都有一些工作要做。智能指针类似于原始指针,但它们并不相同,因此您不能只是查找并替换 T* 的所有实例并将其替换为 my_scoped_ptr code> 并期望它能像以前一样工作。

You can use a technique presented by Boost, but my concern is that you're allowing implicit conversions from a smart pointer to a raw pointer, which is generally frowned upon on. Besides, users can call delete on a pointer obtained by the -> operator, so there's really nothing you can do to prevent a determined idiot to work around whatever mechanism you come up with.

You really should just implement a get() method instead of providing operator T*() so that at least calls to delete smartptr will not compile. Non-idiots should be able to figure out that there's probably a reason why that won't work.

Yes, it's more work to type out LegacyFunc(smartptr.get()) than LegacyFunc(smartptr), but the former is preferred since it makes it explicit and prevents unexpected conversions from happening, like delete smartptr.

What if you have functions like this:

 void LegacyOwnPointer(SomeType* ptr);

where the function will store the pointer somewhere? This will screw up the smart pointer, because now it's not aware that something else is owning the raw pointer.

Either way, you have some work to do. Smart pointers are like raw pointers, but they are not the same, so you can't just find-and-replace all instances of T* and replace it with my_scoped_ptr<T> and expect it to work just as well as before.

远山浅 2024-09-18 04:30:28

没有对此考虑太多,但是...您能否为模板类实例的强类型删除运算符提供重载,以便在包含代码时编译失败?如果这是在您的头文件中,则应防止调用删除时的隐式转换,以支持调用您的重载。

运算符删除(my_scoped_ptr)
{
//...不可编译的代码放在这里

如果这被证明是一个愚蠢的想法,我深表歉意

have not thought much about this but ... Can you provide an overload for operator delete which is strongly-typed for instances of your template class such that when the code is included compilation fails? if this is in your header file then implicit conversion in calls to delete should be prevented in favour of a call to your overload.

operator delete(my_scoped_ptr)
{
//... uncompilable code goes here
}

Apologies if this turns out to be a stupid idea.

故事和酒 2024-09-18 04:30:28

我可以看出你不想在哪里进行大量的 .get() 应用。您是否考虑过用更小的替代品来替代删除?

struct A
{
    friend static void Delete( A* p) { delete p; }

private:
    ~A(){}
};

struct B
{
};

int main() 
   { 

    delete new B();  //ok

    Delete( new A ); //ok

    delete new A; //compiler error

    return (0); 
    } 

I can see where you do not want to do a massive application of .get()'s. Have you ever consider a much smaller replacement of delete's?

struct A
{
    friend static void Delete( A* p) { delete p; }

private:
    ~A(){}
};

struct B
{
};

int main() 
   { 

    delete new B();  //ok

    Delete( new A ); //ok

    delete new A; //compiler error

    return (0); 
    } 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文