Is there a way to specify in C++ that a pointer points to data that is always valid?
在指针(foo(void*bar))的函数参数中,可以使用const指定指针(参数)本身为常量(foo(void*const bar))和/或指针指向的数据为常量(foo(void const*bar))。
然而,在foo(void const*bar)的情况下,这只是向调用者保证foo不会尝试修改bar指向的数据。它并不保证BAR向FOO提供BAR指向的内存位置始终有效的任何保证。
在使用可执行映像中的常量数据的情况下,如果可以向foo提供保证,并且如果foo需要将该数据的引用保留的时间长于函数调用的持续时间,则foo可以简单地保留指针的副本,而不必复制数据。
有没有办法在C++类型系统中对这个保证进行编码?
谢谢。
- 你可以选择一个T const*&,这至少可以确保,无论你手里拿的是什么,在主叫网站上都是const,但我认为没有多大帮助。
- 你总是可以使用像boost标签系统这样的东西,它编码类型系统中的非类型信息。
- 你的意思是问"有没有一种方法可以指定指针指向始终有效的数据"?"是"和"永远是"之间有区别。如果这就是您的意思,那么像shared_ptr这样的标准库包装类之一可能会满足您的需要。
- 您希望为谁的利益对这些信息进行编码?作为编译器的提示?或者作为开发人员阅读/使用代码的一种文档?
- void const *bar不保证函数不会尝试修改数据。它传递意图;函数可以通过丢弃const和修改数据来违反该意图。
- @我的意思是"永远有效"。谢谢,我会更多地研究共享资源(我已经简单地看了一下,但时间不够长,不知道它做了什么)。
- @JALF-这将是开发人员文档。如果我看到一个对象上的函数接受一个指向原始数据的指针,保存该指针,然后稍后再尝试使用它,我会怀疑有一个bug,因为这个对象无法知道指针在函数调用结束后是否仍然有效。
问:有没有一种方法可以在C++中指定指针指向总是有效的数据?
答:不,你总是有能力用脚射自己:)
- 我只看到对这个主题的愚蠢的回应,没有对主题之外提供的细节的确认。对不起(1)。
不是使用原始指针,但您可以使用共享指针或唯一指针,这将表示函数拥有指针的所有权。
- 但这并不意味着指针是有效的。
- @Petebecker不能保证,但如果不能保证,那么调用者会严重滥用共享指针或唯一指针。唯一不能保持有效的方法是调用方做了一些恶作剧,比如从智能指针中拉出原始指针并删除它。
- 我的观点是,使用shared_ptr并不能保证它所保存的指针是有效的,而不仅仅是调用一个函数来保证您传递的指针是有效的。垃圾进,垃圾出。
- 谢谢,我将进一步研究这些指针类型(到目前为止,我只是使用C样式的指针)
- @彼得贝克这个问题没有提到检查指针最初是否有效(你不能这样做),只有在没有调用方删除指针的情况下才能存储指针(智能指针没有调用方的异常行为)。
- @dirkholsopple-问题是如何"向foo保证条指向的内存位置始终有效"。始终"包括"最初。
- @Petebecker共享指针或唯一指针保存无效指针的唯一方法是被调用方滥用。共享指针或唯一指针可以保存无效指针的唯一方法是,如果有人故意用无效指针加载它;默认情况下,它们都初始化为nullptr。这就如同你在C++中得到的任何东西的保证一样。总有人会做一些愚蠢的事情,把事情搞得一团糟。不管怎样,正如他在对另一个问题的评论中提到的,他正在寻找一种避免在释放后使用的方法,这就是我的答案所要解决的问题。
- @dirkholsopple-大多数编程问题都是由于意外错误造成的,例如传递错误指针。我决不认为这种事是蓄意的行为。但我的声明仍然有效:将一个坏指针传递给一个shared_ptr实例和将一个坏指针传递给一个函数一样容易。
- @彼得贝克,这显然不那么容易。创建共享的ptr的典型方法是使用make-shared直接构造它,或者在对构造函数的调用中直接使用new。如果您只执行其中一个操作,就无法将无效指针指向共享指针。
- @德克斯洛普-是的,有很多方法可以确保你不会制造坏的指针。但这不是问题。问题是如何编写函数的接口来保证*任何传入的对象都将**始终有效。为用户编写编码指南并不能保证。
- @彼得贝克,你不能保护自己免受白痴的伤害。在任何情况下,使用共享指针都可以保证一个有效的指针,而不涉及白痴。
我采用的方法是创建一个类表示,它引入一个指定支持数据为static的语义。然后,只需确保它不能被琐碎的构造,或者它的数据被重新分配。
所以,没有直接的语言特性,但是引入语义就足够简单了。
下面是如何防止客户端意外地将标准数据提升为不朽的数据容器的说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template <typename T>
class t_immortal_data_container {
public:
// how clients create t_immortal_data_container<T>,
// avoiding implicit promotions:
static t_immortal_data_container Create(T& pImmortalData) {
return t_immortal_data_container(pImmortalData);
}
~t_immortal_data_container() {
}
public:
...
private:
// private: ensure t_immortal_data_container<T> only can use
// this constructor:
t_immortal_data_container(T& pData) : d_immortalData(pData) {
}
private:
T d_immortalData; /* << as pointer or reference */
private:
// prohibited -- no definition
t_immortal_data_container() /* = delete */ ;
}; |
然后更新程序以接受此类型作为参数,并相应地处理这些情况。
- 谢谢,这似乎是我所期望的一个非常重要的解决方案(尽管在我完全理解它之前,我需要查找"=default;"和"=delete;"语法)
- @马特,不客气。笨重:不是这样。引入这些语义是一种非常轻量级的方法。通常,您将在堆栈上创建它们,并在一些选择的地方需要它们作为参数,以确保语义。语法:语法是C++ 11。= default用于允许使用复制构造函数。= delete用于删除默认的构造函数。这仅仅意味着客户端可以使用复制构造函数,而禁止使用默认构造函数。我会改变它,所以它不使用C++ 11…
- 谢谢贾斯汀。当我写上最后一条评论的时候,我有一个错误的印象,你写这个类的时候希望数据本身被封装在里面,而不是仅仅持有一个指向静态数据的指针,所以它比我最初想象的要轻。
- @Matt完全正确-它只是通过指针或引用引用引用静态数据。在某些情况下,您可能只使用它的指针作为最终存储位置(静态是这个容器,并创建数据的单个实例作为静态实例构造函数的参数)。传递容器可以简化一些事情,并防止一些错误。即使这样,也不会花费太多,因为不需要复制数据;只需确保T是指针或引用。我尝试最小化静态数据,所以实际上在很多地方都不使用它,但有时确实有用。
如果接受一个指针作为输入,就没有真正的方法来保证它。但是,您可以维护一个包含有效指针的表,并让您的输入成为该表的索引;您可以通过确保索引位于该表中来验证这一点。
除此之外,您所能做的最好的就是捕获在使用错误指针时发生的异常/信号,并尝试从中恢复。
- 好吧,捕捉使用错误指针时可能发生的异常/信号。
- 谢谢,但我特别想避免在免费情况下使用。
- @马特——如果你事先提到的话,可能会很有帮助,因为这和你所要求的完全不同。有调试工具来检测这种情况(Linux上的valgrind,Windows上的视觉泄漏检测检测器),以便验证代码。如果您正在寻找一个不仅仅是调试工具的东西,那么您将需要自己实现它——也许您的调用者不分配自己的内存,而是必须使用您提供的例程来分配和释放它。