【问题标题】:Would it make sense to have a 'constify' operation in C++?在 C++ 中进行“constify”操作是否有意义?
【发布时间】:2011-04-03 20:20:54
【问题描述】:

C/C++ 中创建一个变量const 的“constify”操作是否有意义?

这是一个可能有用的示例,显然我们不想在第一行声明它const

std::vector<int> v;
v.push_back(5);
constify v; // now it's const

目前,如果没有这种可能性,您必须引入另一个变量才能获得相同的效果:

std::vector<int> v0;
v0.push_back(5);
const std::vector<int>& v = v0;

这更令人困惑,因为它在作用域中添加了一个新名称,您需要将其作为引用以避免复制整个向量(或使用swap?)。

【问题讨论】:

  • 如果你只在 if-then-else 的一个分支中构造 v 会发生什么?
  • 那么它可能应该是const 只在那个范围内。
  • 没有。这是没有意义的。
  • 在我看来,@dehmann 正在寻找 Ruby 的 freeze 操作的 C++ 等价物。虽然我同意在 C++ 程序中需要这种设备通常表明存在设计缺陷,但我也认为这里的操作词是“一般”。我想不出它的有效用途,但也许有一个......
  • 请从您的标题和标签中取出 C。这完全是一个 C++ 问题,我猜无法改变 C 中的静态类型。而这一切出现的“C/C++”真的很伤我的眼,C和C++是两种不同的语言。

标签: c++ variables language-design syntactic-sugar


【解决方案1】:

坦率地说,如果变量是否为const,我发现它较少令人困惑,而不是如果它可以改变。


详细说明一下:您通常想要这样做的原因是因为您无法按照您想要的方式初始化const 变量。 std::vector 就是一个很好的例子。好吧,这一次,下一个标准引入了一种通用的初始化语法,使这成为可能:

const std::vector<int> cvi = { 1, 2, 3, 4, 5, 42 }; 

然而,即使手头没有 C++1x 的东西,即使类型不允许这种初始化语法,你也可以创建一个辅助函数来做你想做的事:

const std::vector<int>& cvi = create_my_vector();

或者,如果你想变得花哨:

const std::vector<int>& cvi = compile_time_list<1,2,3,4,5,42>::create_vector();

注意&amp;。复制函数调用的结果是没有意义的,因为将右值绑定到 const 引用会延长其生命周期,直到引用生命周期结束。
当然,使用支持 C++1x 移动语义的编译器重新编译将使此类优化变得非常不必要。但是将 rvlaue 绑定到 const 引用可能仍然比移动向量更快,并且不太可能更慢。
使用 C++1x,您还可以创建 lambda 函数来执行此操作。 C++ 只是提供了令人难以置信的庞大工具库。 IME,不管你怎么想,其他人应该想出另一个想法来做同样的事情。而且通常比你的更好。


然而,IME 这个问题通常只是因为函数太少而代码太多。然后它不仅适用于 constness,还适用于类似的特征——比如引用所指的内容。
一个经典是使用几个可能的流中的一个。而不是这个

int main(int argc, char* argv[])
{
  std::istream* istrm = NULL;
  std::ifstream ifs;
  if( argc > 1 )
  {
    ifs.open( argv[1] );
    if( ifs.good() ) 
      istrm = &ifs;
  }
  if( !istrm ) 
    istrm = &std::cin;

  while( istrm->good() )
  {
     // reading from *istrm implemented here
  }
  return 0;
}

只需将关注点分为 1) 确定从哪里读取和 2) 实际读取:

int read(std::istream& is)
{
  while( is.good() )
  {
     // reading from is implemented here
  }
  return 0;
}

int main(int argc, char* argv[])
{
  if( argc > 1 )
  {
    std::ifstream ifs( argv[1] );
    if( ifs.good() ) 
      return read(ifs);
  }
  return read(std::cin);
}

我还没有看到一个真实世界的变量示例,该示例无法通过分离关注点来修复它本来可以保持不变的变量。

【讨论】:

  • +1:我必须同意——const 在执行 ctor 或 dtor 期间不适用的事实已经引起了足够的混乱。
  • 我认为您的意思是 C++0x,而不是 C++1x。 www2.research.att.com/~bs/C++0xFAQ.html
  • @Ben:C++0x 的时间框架今年结束,今年不会有新标准。它可能会在明年问世,这将使它成为 C++11。如果有进一步的延迟,它将是 C++12。因此是 C++1x。
  • 您阅读我的链接了吗? “‘C++0x’这个名字是我和其他人希望 C++08 或 C++09 的遗留物。但是,为了尽量减少混淆,我将继续提及即将到来的 C++ 标准此处定义为 C++0x 的功能集。将“x”视为十六进制(很可能是“B”,即 C++11)。” - Bjarne Stroustrup
  • @Ben:是的,我很久以前做过。但是,我不同意他的观点。当“C++0x”指代“C++11”时,我发现它更多令人困惑。
【解决方案2】:

考虑以下几点:

void foo(std::vector<int> & v) 
{
  v.push_back(1);
  constify v;
}
void bar() {
  std::vector<int> test(7);
  foo(test);
  test.clear();
}

foo 中的变量v 是否被约束?它与 bar 中的test 变量相同。因此,test.clear() 调用应该是无效的。我认为您真正的意思是名称是“固定的”,而不是变量。

指定和实现实际上是微不足道的:constify x; 是一个名为 x 的 const 引用的声明,它与它隐藏的变量 x 具有相同的基类型。它遵循通常的范围规则,除了它可以定义在与之前的x 声明相同的范围内。

【讨论】:

  • 是的,我同意这个名字会被混淆。它总是只适用于当前范围。而且您在使用const&amp; 暗示与我在问题中给出的相同的实现。所以是的,编译器只需要插入const&amp; 变量来进行这个简单的程序转换。
  • +1 不幸的是,迟到的答案很少能获得足够的赞成票以登上榜首。其他几个答案都不错,但这可能是最好的。
【解决方案3】:

已经提到 C++0x 使用大括号初始化器在一定程度上解决了这个问题:

const std::vector<int> values{1, 2, 3, 4, 5};

虽然这只允许初始化,并且不允许,例如,在构造函数运行后调用非const 成员函数。 可以定义一个宏constify如下:

#define constify(type, id) \
for (type const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

可以这样使用:

std::vector<int> v;

// v is non-const here.

constify (std::vector<int>, v) {

    // v is const here.

}

这通过设置一个 for 循环来工作,该循环只执行一次以下语句或块,其中 constified 变量位于循环体的本地。注意辅助变量 i_const 在本地 i 之前的声明:声明 int const&amp; i(i)i 初始化为 自身 - 即,未初始化的值 - 我们想要 (i)改为引用 previously 声明的 i,因此需要一个额外的级别。

如果您可以使用 C++0x 功能,decltype 关键字会派上用场,允许您在调用 constify 时省略类型:

#define constify(id) \
for (decltype(id) const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

这让你可以写,简单地说:

constify (v) {
    // ...
}

无论变量最初是否声明为const,这两个版本都有效。所以,是的,与您正在寻找的非常相似的东西确实是可能的,但可能完全不值得。

【讨论】:

  • 以这种方式使用 const_cast 会导致未定义的行为。
  • 这是不安全的,因为每当您将变量声明为const 时,编译器可能会选择将其全部或部分放入只读内存中,如果您丢弃 const 和尝试修改对象。这对于像 std::vector 这样的复杂类来说不太可能,但仍然是一个问题。
  • 哦,好吧。我确实说过这不安全。
  • 我删除了不正确的部分并进一步澄清了constify实现的解释。
【解决方案4】:

我假设您谈论的不仅仅是初始化向量(这在 C++0x 中已解决)更通用,并且仅使用向量作为示例。

我宁愿看到它通过某种本地函数来完成:

const vector<int> values = []{
    vector<int> v;
    copy(some_other_data.begin(), some_other_data.end(), v);
    sort(v);
    return v;
}();

(我可能会弄乱 C++0x 的匿名函数语法)。我可以很自然地理解为:“根据此处描述的例程准备一个 const 向量”。只有括号的数量让我有点困扰。

在 C++0x 对程序员来说变得更加自然之后,我可以看到这段代码如何成为 C++ 习惯用法。

(根据德曼的建议编辑)

【讨论】:

  • 不错的答案。是的,您似乎不需要[](){...} 中的所有括号。圆括号是可选的,所以它变成[]{...}
【解决方案5】:

目前,const 与否是编译器知道的,因此编译器不会接受试图更改 const 变量的程序。

如果您想创建constify 运算符,则必须将其设为变量的属性(每个 变量的其他关键字),以便它可以在运行时更改。当然,每当程序尝试更改(当前)const 变量时,您都必须抛出异常,这实际上意味着对每个变量的每次写入访问都必须首先检查 const 属性。

所有这些都违背了 C++ 和其他所有静态类型语言的哲学。它还破坏了与现有库的二进制兼容性。

【讨论】:

    【解决方案6】:

    您可以将向量包装在一个类中,将包装的向量声明为可变的,然后创建一个包装器的 const 实例。包装类可以更改向量,但外部调用者会看到一个 const 对象

    【讨论】:

      【解决方案7】:

      我也想过这个问题。但是,恕我直言,它会造成很多混乱,这将超过它的好处。想想看,C++ 中 constness 的整个概念已经够混乱了。

      您的想法归结为“如何在变量初始化后将其设为只读?”。您可以通过将变量设为类的私有成员来获得相同的效果,该类在构造函数中初始化,并且您为其提供 getter 但不提供 setter。

      【讨论】:

        【解决方案8】:

        C++ 是静态类型的。对我来说,引入这样的操作会违反这个范式,会引起很多混乱。

        【讨论】:

        • 这仍然是静态类型——编译器可以在编译时推断变量何时为 const 或不是。
        • @liori:也许吧。如果变量的类型在执行过程中可以改变,这意味着我们需要进行执行路径分析来进行静态类型化。这并非在所有情况下都有效,并且会在其他情况下导致令人惊讶的结果。
        【解决方案9】:

        您基本上是在尝试重现构造函数的效果——即,const 仅在构造函数完成后应用(并且仅在调用 dtor 之前)。因此,您需要另一个类来包装您的向量并在 ctor 中对其进行初始化。一旦 ctor 完成并返回,实例变为 const(当然,假设它被定义为 const)。

        C++0x 将大大改善这样的包装要求。您将能够对向量使用大括号初始值设定项,以便在一次操作中创建/初始化向量。其他类型将(至少可能)支持用户定义的初始化器来完成大致相同的事情。

        【讨论】:

          【解决方案10】:

          现在是使用函数的好时机

          #include <vector>
          
          std::vector<int> makeVector()
          {
            std::vector<int> returnValue;
            returnValue.push_back(5);
            return returnValue;
          }
          
          int main()
          {
            const std::vector<int> myVector = makeVector();
          }
          

          【讨论】:

          • 您的解决方案具有不同的语义。 OP 想要创建 1 vector&lt;T&gt; 并更改标识符的 const 非常量。您的解决方案使用 2 个不同的访问权限创建 2 个 vector&lt;T&gt; 副本
          • @Jared:我将其解读为:OP 本质上想要创建一个常量向量,但由于缺少 C++0x 的初始化程序列表,或者没有同时拥有所有数据而不能.
          • @Jared:这个版本可能只会创建一个向量(由于命名返回值优化),您可以在一个范围内对其进行修改,然后在另一个范围内变为常量,就像 OP 所希望的那样。跨度>
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-07-11
          • 1970-01-01
          • 2011-01-14
          • 2011-02-09
          • 1970-01-01
          相关资源
          最近更新 更多