【问题标题】:boost::optional not letting me reassign const value typesboost::optional 不允许我重新分配 const 值类型
【发布时间】:2012-07-12 19:31:36
【问题描述】:

在我看来boost::optional应该有四种变体

  • optional<Foo> => 持有可变的 Foo 并且可以在初始化后重新分配

  • optional<Foo const> const => 持有一个 const Foo,初始化后不能重新赋值

  • optional<Foo> const =>(应该?)持有一个可变的 Foo 但初始化后不能重新分配

  • optional<Foo const> =>(应该?)持有一个 const Foo 并且可以在初始化后重新分配

前 2 个案例按预期工作。但是optional<Foo> const 取消对 const Foo 的引用,而optional<Foo const> 不允许在初始化后重新分配(如this question 中所述)。

const 值类型的重新分配是我遇到的具体情况,错误是:

/usr/include/boost/optional/optional.hpp:486:错误:将 'const Foo' 作为 'Foo& Foo::operator=(const Foo&)' 的 'this' 参数传递会丢弃限定符 [-fpermissive]

它发生在这里:

void assign_value(argument_type val,is_not_reference_tag) { get_impl() = val; }

在构造之后,实现将赋值运算符用于您参数化可选项的类型。它显然不想要一个 const 值的左操作数。但是为什么你不能将一个非 const 可选项重置为一个新的 const 值,例如在这种情况下:

optional<Foo const> theFoo (maybeGetFoo());
while (someCondition) {

    // do some work involving calling some methods on theFoo
    // ...but they should only be const ones

    theFoo = maybeGetFoo();
}

一些问题:

  • 我是否正确,想要这个在概念上是好的,而不能做到这一点只是实施中的侥幸吗?

  • 如果我不编辑 boost 源,那么在不完全废弃 boost::optional 的情况下实现上述循环中的逻辑的干净方法是什么?

  • 如果这确实有意义并且我要编辑 boost::optional 源(我已经不得不这样做support movable types,尽管我怀疑他们很快就会自己这样做)那么有哪些微创改变可以解决问题呢?

【问题讨论】:

  • 考虑Foo const* p = /*...*/ ; *p = Foo();。现在将指针替换为可选的:optional&lt;Foo const&gt; p; *p = Foo();。相同的行为。
  • boost::optional&lt;T&gt; 本质上是T 的替代品。如果你有一些 const,那么你不要重新分配它。如果您想重新分配它,请不要将其设为 const。
  • 虽然这不是所问的,但问题可能更多的是针对Foo const *p = gen(...);p=gen(...);。这应该适用于Foo const *,但不适用于optional&lt;const Foo&gt;。
  • @Managu 因为 optional 存在以提供值语义,而带有指针的示例正在处理引用语义。
  • optional&lt;Foo&gt; 和 optional&lt;const Foo&gt; 都为用户制作副本。为什么要限制其副本的常量性?从函数返回一个对象的常量副本给用户似乎是一样的。

标签: c++ boost assignment-operator const-correctness boost-optional


【解决方案1】:

所以基本上问题似乎与documentation for optional&amp; optional&lt;T (not a ref)&gt;::operator= ( T const&amp; rhs ) 中的这条注释有关:

注意:如果 *this 已初始化,则使用 T 的赋值运算符,否则使用其复制构造函数。

也就是说,假设您有boost::optional&lt;const Foo&gt; theFoo;。由于默认构造的boost::optional&lt;&gt; 为空,因此语句:

theFoo=defaultFoo;

应该表示“将构造 defaultFoo 复制到 theFoos 内部存储中。”由于该内部存储中已经没有任何内容,因此这是有道理的,即使内部存储应该容纳const Foo。完成后,theFoo 不会为空。

一旦theFoo 包含一个值,语句

theFoo=defaultFoo;

应该表示“将defaultFoo 分配给theFoos 内部存储中的对象。”但是theFoos 的内部存储是不可分配的(因为它是const),所以这应该会引发一个(编译时间?)错误。

不幸的是,您会注意到最后两个语句是相同的,但在概念上需要不同的编译时行为。不过,没有什么可以让编译器分辨出两者之间的区别。


特别是在您描述的场景中,将boost::optional&lt;...&gt; 的赋值运算符定义为具有语义可能更有意义:

如果 *this 被初始化,它的当前内容首先被销毁。然后使用T的复制构造函数。

毕竟,如果您确实想要这样做,完全可以调用T 的赋值运算符,通过说*theFoo = rhs。

【讨论】:

  • 类和模板非常通用,我相信@KerrekSB 在上面得到的是并非所有东西都是“容器”。 boost::optional 表面上可能看起来像一个,但它的设计目的是在一个类型上添加一个“存在位”......但在其他方面表现得像那个类型。按照这种逻辑,我认为它适合设计模式对象的类别,可能更像"proxy" 或“增强器”或其他东西(这些术语都有点模糊)......但关键是它不是一个“容器” . :-/
  • @HostileFork:我不同意。 boost::optional&lt;T&gt; 的行为与 T 完全不同。如果你想获得/作用于底层T,你通常必须取消引用:boost::optional&lt;int&gt; opt_i(6);/std::cout&lt;&lt;*opt_i&lt;&lt;std::endl;。因此,我认为 boost::optional&lt;T&gt; 被认为是 0 或 1 Ts 的容器。
  • 我当然看到了你的一面(这就是我写这个问题的原因!:-P)但是除了阅读的额外困难之外,考虑写作......如果你的函数的返回类型是optional&lt;T&gt; ,您可以只返回一个T 值。所以在这方面有点不对称。
  • +1 在任何情况下的研究......尽管它更多的是对问题的阐述而不是答案。 :)
  • 我结束了self-answering 这个。请注意我对取消引用 const optional&lt;Foo&gt; 如何让您获得 const Foo 的问题的更新。我几乎已经转变为相信这是正确的想法......
【解决方案2】:

(1) 一个人对“应该”行为的看法取决于选项是 “零个或一个任意类型对象的容器” 还是 em>“一种类型的瘦代理,具有附加功能”。现有代码采用后一种思路,通过这样做,将列表中的“四种不同行为”去掉了一半。这降低了复杂性,并防止您无意中引入低效的用法。

(2) 对于任何值可复制的Foo 类型,可以通过创建一个新选项轻松地在可变选项和不可变选项之间切换。因此,在给定的情况下,您会简单地将其设为可变值,然后将其复制到不可变值中。

optional<Foo> theMutableFoo (maybeGetFoo());
while (someCondition) {
    optional<Foo const> theFoo (theMutableFoo);

    // do some work involving calling some methods on theFoo
    // ...but they should only be const ones
    // ...therefore, just don't use theMutableFoo in here!

    theMutableFoo = maybeGetFoo();
}

考虑到它是一个类型的“瘦代理”的模型,如果类型没有包含在可选项中,这与你必须做的事情完全相同。在这种情况下,普通的 const 值类型需要同样的处理。

(3) 必须跟进information given by @Andrzej 才能找到答案。但是这样的实现更改可能不会比每次都创建一个新的可选(如上面的循环)更好。最好接受现有的设计。


来源:@R.MartinhoFernandez、@KerrekSB

【讨论】:

    【解决方案3】:

    回答你的三个问题:

    1. 实现遵循 optional 赋值使用 T 赋值的设计理念;从这个意义上说,实施很好。
    2. optional 的设计考虑了可能的扩展。 Optional 只是底层类 optional_base 的接口。您可以从 optional_base 派生自己的类,而不是使用 optional。 optional_base 有一个受保护的成员结构,它几乎可以满足您的需要。您将需要一个新成员,例如 reset(T),它首先清除 optional_base,然后调用construct()。
    3. 或者,您可以将成员 reset(T) 添加到可选中。这将是影响最小的更改。

    您也可以尝试this proposal 中 optional 的参考实现。

    【讨论】:

    • 我尝试了参考实现,感谢您的链接......它似乎期望 T 是默认可构造的以便工作,这有点退后一步(!)我是对扩展optional_base 的想法很感兴趣,尽管现在我想知道是否可以用“reference_wrapper”之类的东西来解决这个问题,除非它是“value_wrapper”之类的东西......具有同样的意图。 ?
    • @HostileFork:无论您所说的“value_wrapper”是什么意思,我很确定它与 reference_wrapper 有很大不同:后者总是作为单个指针实现。在您的情况下,您可能需要一个对齐的存储(大小和对齐的 T)和一种手动控制对象的构造和销毁的方法。这就是 optional_base 可以帮助您的地方。
    • 我结束了self-answering 这个。请注意我对取消引用 const optional&lt;Foo&gt; 如何为您提供 const Foo 的问题的更新。我几乎已经转变为相信这是正确的想法......
    猜你喜欢
    • 1970-01-01
    • 2019-08-09
    • 2023-04-02
    • 2020-12-02
    • 2021-11-01
    • 1970-01-01
    • 2018-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多