【问题标题】:How to compose mutable objects const correctly如何正确组合可变对象 const
【发布时间】:2014-11-21 17:23:35
【问题描述】:

如果您尝试传递对临时对象的引用,则需要const

http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=EN-US&k=k%28C4239%29;k%28vs.output%29&rd=true

这意味着在 C++ 中不可能将对象的可修改包装器建模为临时对象(无需强制使对象成为左值):

inline char * PcToUnix(AutoCStringBufferA & buffer) { return PcToUnix(buffer, buffer.size()); }
inline char * PcToUnix(CStringA & str) { return PcToUnix(make_autobuffer(str)); }

PcToUnix :将字符缓冲区从 CR+LF 原位转换为 CR。 make_autobuffer :接受 CString,并锁定其底层字符缓冲区,以便我们可以在 autobuffer 的生命周期内直接访问以对其进行操作。

所以我不能编写一个调用语句,它采用底层字符串,将其包装在缓冲区管理对象(可变)中,将其传递给操作缓冲区内容的函数并返回,除非 autobuffer 是声明为const &

错误:

inline char * PcToUnix(AutoCStringBufferA & buffer) { return PcToUnix(buffer, buffer.size()); }

好吗?!

inline char * PcToUnix(const AutoCStringBufferA & buffer) { return PcToUnix(buffer, buffer.size()); }

但“正确”的形式似乎指定了以下合同:“我不会修改您的缓冲区”。这里的 constness 在 autobuffer 对象的级别上是逻辑上正确的 - 它在 PcToUnix() 期间没有被修改 - 缓冲区包装器对象本身(即 autobuffer)在 PcToUnix 期间没有被修改 - 所以它是确实是const...

但是,但是,它正在修改其底层对象 - 根据定义 - 其他东西的包装器 - 并且其他东西被修改了。

在我看来,这似乎是 C++ const 的一个根本缺陷。没有办法同时满足当前规则(不使用非标准编译器行为或编写误导性 API 合同或编写不必要的冗长代码。

详细方法:

inline char * PcToUnix(CStringA & str) { auto adapter = make_autobuffer(str); return PcToUnix(adapter); }

误导性的 API 方法:

inline char * PcToUnix(const AutoCStringBufferA & buffer) { return PcToUnix(buffer, buffer.size()); }
inline char * PcToUnix(CStringA & str) { return PcToUnix(make_autobuffer(str)); }

如果 C++ 提供了一种方法来指定包装器的 constness 与被包装对象的 constness 分开,那么我们就有了一种健全的方法来创建规则并简洁地表达 API。但我只是不知道如何做到这一点。

预智能指针(或对于所有没有包装层的上下文)可以单独指定指针的常量与底层对象的常量:

T * const pConstPointerToMutableObject;
const T * pMutablePointerToConstObject;
const T * const pConstPointerToConstObject;

但是对于包装对象没有任何相应的支持。没有办法将referer的constness与referee的constness分开来表达。也没有任何一套合理的规则来定义或控制const 的交换/关联/分配性质。

我不明白为什么没有经常提出这个问题。我经常偶然发现这一点,并发现const 是一个巨大的 PIA,因为它

理想情况下,代码应该是可堆肥的——即,应该能够将一些更简单的对象包装在另一层以某种方式适应它——也许过滤访问,或添加有关管理子对象的智能,或延迟底层事物的实例化,等等,这样就可以建立一个数据的复杂和特定的接口,而不必一遍又一遍地重新设计基本对象——只需包裹在一层或两层中,为上下文添加必要的智能/逻辑/接口/适应这是需要的。

但由于 const 正确性问题,C++ 几乎不可能做到这一点。 (或者,我太愚蠢了,无法弄清楚如何做这一切,在我广泛的阅读中,我已经设法错过了如何做,甚至没有人在讨论这些问题而只是掩饰并忽略const 的这一方面)。也许这里的某人可以免除我的任何错误??

【问题讨论】:

  • “但是没有任何对包装器对象的相应支持。” unique_ptr<const T>
  • "const 如果您尝试传递对临时对象的引用,则需要。" 首先解释您为什么要这样做。并考虑右值引用。
  • 临时销毁后使用PcToUnix的返回值会怎样?
  • PcToUnix 是一个 void 函数 - 它只会在处理它的 char 缓冲区中产生副作用。我意识到这个规则的存在是为了避免你试图修改一个临时的,然后在任何人看到副作用之前将其销毁。但是,对于包装器/适配器类型,副作用是在底层对象中,而不是在临时包装器中 - 因此处理它是完全合法的,并且没有任何损失。
  • unique_ptr<const T> 只是一个带有 API 的包装器对象的反例,它允许您指定裁判的常量。

标签: c++ wrapper const-correctness


【解决方案1】:

实现 const 正确性的简单方法:使用传递性 const。

您似乎对免费的要求有点过高在语言级别。它实际上是经过设计的(除其他外),以​​便您为使用的东西付费,并确保安全和高性能。是的,它可能非常冗长。

就物理常量感知的容器而言,C++ 肯定能够支持这种区别;例如:

void A(const std::shared_ptr<const bool>& p) { /* ... */}
void B(const std::shared_ptr<bool>& p) { /* ... */}

void C() {
 A(std::make_shared<bool>(false)); // << ok
 A(std::make_shared<const bool>(false)); // << ok

 B(std::make_shared<bool>(false)); // << ok
 B(std::make_shared<const bool>(false)); // << error. API forbids removal of const.
}

但 C++ 标准库并没有提供您可能需要的所有容器来完成您的要求。

列举您需要的智能指针变体,并考虑它们负责对象的生命周期以及从一个容器到另一个容器的转换。另请注意,如果您仅支持传递性 const,该列表会短多少。然后考虑 OP 中的 API 通常会转换为模板以轻松支持变体。

有时,您必须处理接口不太理想的类型。在这些情况下,为它们制作一个容器并引入您理想的 const 形式通常是最容易的。

使用这种方法,容器完成了大部分繁重的工作(=使您免于调用现场的冗长)。

当然,类似 C++ 的字符串转换方式看起来更像:

std::string PCToUnix(std::string pString) {
 ...mutate pString...
 return pString;
}

或就地:

void PCToUnix(std::string& pString) {
 ...mutate pString...
}

【讨论】:

  • 因此,如果我理解正确并将其应用于我的包装器,那么您建议PcToUnix 应该采用const AutoCStringBuffer&amp;,因为包装器是const,就像@ 987654328@ 将底层对象的常量性与智能指针本身区分开来。我的错误是试图将const MyWrapper &amp; 视为暗示包装对象的常量性,而这不是 C++ 所说(或做)的?
  • @Mordachai 我是说传递 const 是理想的。 transitive const 一个 D 概念/术语,与 C++-term logical const 最相似:dlang.org/const-faq.html#transitive-const。使用传递 const,您的函数将被禁止访问对 const 参数的内部字符缓冲区的可变引用(并因此改变)。因此,将需要非常量引用。考虑到函数返回其参数的内部指针,作为 const 传递可能是不明智的,因为函数调用可能需要创建一个临时的。 (续)
  • @Mordachai 如果您不喜欢传递性 const,那么您可能只有少数容器/适配器无法完成您所追求的工作,同时减少了一些语法噪音。详尽的容器集合将是无数的。您可以将这些容器设计得更安全。使用此类容器,您的功能通常需要迁移到模板。标准库不提供所有这些智能指针的变体——只是最基本的所有权模型的简单实现。
  • @Mordachai 差不多。关于指定这样的容器/适配器,我有意抽象,因为有许多可能的变化。涉及的典型变体是constmutable、指针、值、引用、所有权、单元素和多元素容器——但存在其他限定符和要求。实际上,您可以在没有所有可能的变体的情况下度过难关。可以创建基本容器来引入您想要的语义和转换。 (续)
  • @Mordachai const/mutable 名称是一个选项,但通常适用于逻辑 const 设计(因为使用物理 const 的设计有一种传输可变引用的方法)。我使用传递常量,并且我已经拥有比我想要的更多的容器:) 但是是的,它是一个定义容器的选项,它引入了您使用/想要的语义和转换——比原始指针/引用具有更高的安全性。有人建议在这些领域改进 lang/std 库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-14
  • 1970-01-01
  • 2021-02-10
  • 2011-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多