7

在其中产生变量的“ constify”操作是否有意义?C/C++const

这是一个可能有用的示例,显然我们不想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?)。

4

10 回答 10

19

坦率地说,如果一个变量是不是,我发现它比如果它可以改变更容易混淆。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();

注意&. 复制函数调用的结果是没有意义的,因为将右值绑定到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);
}

我还没有看到一个真实世界的变量示例,它不像它本来可以的那样恒定,并且无法通过分离关注点来修复。

于 2010-08-25T16:34:20.823 回答
8

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

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

于 2010-08-25T16:39:28.523 回答
7

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

于 2010-08-25T16:40:00.347 回答
6

这是使用函数的好时机

#include <vector>

std::vector<int> makeVector()
{
  std::vector<int> returnValue;
  returnValue.push_back(5);
  return returnValue;
}

int main()
{
  const std::vector<int> myVector = makeVector();
}
于 2010-08-25T16:34:46.503 回答
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++ 习惯用法。

(根据德曼的建议编辑)

于 2010-08-25T17:00:54.680 回答
3

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

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

于 2010-08-25T16:40:39.877 回答
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请注意在 local 之前的辅助变量声明i:语句int const& i(i)初始化i自身——即,初始化为未初始化的值——并且我们希望(i)引用先前声明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。所以,是的,与您正在寻找的非常相似的东西确实是可能的,但可能完全不值得。

于 2010-08-25T16:49:58.350 回答
2

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

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

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

于 2010-08-25T18:18:01.870 回答
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是 constified 吗?它与 bar 中的变量相同test。因此,test.clear()调用应该是无效的。我认为您真正的意思是名称是“固定的”,而不是变量。

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

于 2010-08-26T08:59:04.443 回答
0

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

于 2010-08-25T17:02:13.947 回答