【问题标题】:Is there a way to specify in C++ that a pointer points to data that is always valid?有没有办法在 C++ 中指定指针指向始终有效的数据?
【发布时间】:2012-08-28 11:55:32
【问题描述】:

在作为指针 (foo(void *bar)) 的函数参数中,您可以使用 const 来指定指针(参数)本身是常量 (foo(void * const bar)),和/或指针指向的数据是常量(foo(void const *bar))。

但是在 foo(void const *bar) 的情况下,这只是向调用者保证 foo 不会尝试修改 bar 指向的数据。它不给 bar 任何保证 foo 指向的内存位置将始终有效。

如果您在可执行映像中处理常量数据,如果您可以向 foo 提供该保证,并且如果 foo 需要保留对该数据的引用超过函数调用的持续时间,则 foo 可以简单地保留一个复制指针而不是复制数据。

有没有办法在 C++ 类型系统中编码这个保证?

谢谢。

【问题讨论】:

  • 您可以使用T const*&,这至少可以确保在呼叫者网站上您收到的任何邮件都是const,但我认为这没有多大帮助。
  • 你总是可以使用诸如 boost 标签系统之类的东西,它在类型系统中编码非类型信息。
  • 您的意思是要问“有没有办法指定指针指向的数据总是有效”? “是”和“永远是”是有区别的。如果这就是你的意思,那么像 shared_ptr 这样的标准库包装类之一可能会做你想做的事。
  • 您希望对这些信息进行编码是为了谁的利益?作为对编译器的提示?还是作为开发人员阅读/使用代码的一种文档?
  • void const *bar 不保证函数不会尝试修改数据。它传达意图;该函数可以通过丢弃 const 并修改数据来违反该意图。

标签: c++ constants


【解决方案1】:

问:有没有办法在 C++ 中指定指针指向始终有效的数据?

答:不。你总是有能力射自己的脚:)

【讨论】:

  • 我只看到对这个主题的愚蠢回应,并且没有确认主题之外提供的细节。抱歉 (-1)。
【解决方案2】:

不使用原始指针,但您可以使用 shared_ptr 或 unique_ptr 代替,这将传达函数拥有指针的所有权。

【讨论】:

  • @PeteBecker 不能保证,但如果不是,则调用者严重滥用 shared_ptr 或 unique_ptr。它不会保持有效的唯一方法是调用者做了一些恶作剧,比如将原始指针从智能指针中拉出来并删除它。
  • 我的意思是,使用shared_ptr 并不能保证它持有的指针是有效的,就像调用一个函数可以保证你传入的指针是有效的一样。垃圾进,垃圾出。
  • 谢谢,我会再研究一下那些指针类型(目前我只是使用 C 风格的指针)
  • @PeteBecker 问题没有提到检查指针最初是否有效(你不能这样做),只是你是否可以存储指针而不调用者删除它(智能指针做短调用者的非常不寻常的行为)。
  • @DirkHolsopple - 问题询问如何“向 foo 保证 bar 指向的内存位置将始终有效”。 “总是”包括“最初”。
【解决方案3】:

如果您接受指针作为输入,则没有真正的方法可以保证它。但是,您可以维护一个有效指针表,并让您的输入成为该表的索引;您可以通过确保索引位于表内来验证这一点。

除此之外,您能做的最好的事情就是捕获使用错误指针时发生的异常/信号并尝试从中恢复。

【讨论】:

  • 好吧,捕捉在使用错误指针时可能发生的异常/信号。
  • 谢谢,但我特别想避免使用后释放的情况。
  • @Matt -- 如果您事先提到这一点,它可能会很有帮助,因为它与您所要求的完全不同。有一些调试工具可以检测这种情况(Linux 上的 valgrind,Windows 上的可视泄漏检测检测器),以验证代码。如果您正在寻找不仅仅是调试工具的东西,您需要自己实现它——也许您的调用者不分配自己的内存,而是他们必须分配和使用您提供的例程释放它。
【解决方案4】:

我采用的方法是创建一个类表示,它引入了一个语义,指定支持数据是static。然后确保它不能被简单地构造,或者它的数据重新分配。

所以不,没有直接的语言特征,但引入语义很容易。

这是一个关于如何防止客户端意外将标准数据提升到不朽数据容器的示例:

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;”语法)
  • @Matt 不客气。 重: 不是这样。这是引入这些语义的一种非常轻量级的方式。通常,您将在堆栈上创建它们,并在几个选定的位置将它们作为参数来确保语义。 语法: 语法是 C++11。 = default 用于允许使用复制构造函数。 = delete 用于删除默认构造函数。这只是意味着客户端可以使用复制构造函数,同时禁止使用默认构造函数。我将对其进行更改,使其不使用 C++11……
  • 谢谢贾斯汀。当我写最后一条评论时,我误以为你写这个类是希望数据本身被封装在其中,而不是仅仅持有一个指向静态数据的指针,所以它比我最初想象的要轻。
  • @Matt 完全正确 - 它只是通过指针或引用引用您的静态数据。在某些情况下,您可能只是将其指针用作确定的存储位置(静态是此容器,并创建数据的单个实例作为静态实例的构造函数的参数)。传递该容器可以简化一些事情,并防止一些错误。即使那样,它也不会花费太多,因为不需要数据副本;只要确保T 是一个指针或引用。我尝试最小化静态数据,所以我实际上并没有在很多地方使用它,但它有时确实有帮助。
猜你喜欢
  • 1970-01-01
  • 2012-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-24
  • 1970-01-01
相关资源
最近更新 更多