在C ++中进行'constify'操作是否有意义?

时间:2010-08-25 16:31:23

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

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?)。

10 个答案:

答案 0 :(得分:19)

坦率地说,如果变量是const或者不是const,我发现 会比这更加困惑。


详细说明一下:您通常想要这样做的原因是因为您无法按照您希望的方式初始化std::vector变量。 const std::vector<int> cvi = { 1, 2, 3, 4, 5, 42 }; 就是一个很好的例子。好吧,有一次,下一个标准引入了通用的初始化语法,使这成为可能:

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

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

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

或者,如果你想要花哨:

&

请注意const。复制函数调用的结果没有意义,因为将右值绑定到const引用会延长其生命周期,直到引用的生命周期结束。
当然,使用支持C ++ 1x'移动语义的编译器重新编译将使这种优化变得非常不必要。但是将rvlaue绑定到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; } 引用可能仍然比移动向量更快并且不太可能更慢。
使用C ++ 1x,您也可以创建执行此操作的lambda函数。 C ++只是提供了一个非常庞大的工具库。 IME,无论你多么努力,别人都应该提出另一个想法来做同样的事情。通常比你的更好。


然而,IME这个问题通常只会在函数太少的情况下带来太多代码。然后它不仅适用于常数,而且适用于类似的特征 - 就像参考文献所指的那样 经典之作是使用多个可能的流中的一个。而不是这个

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)找出从哪里读取和2)实际阅读:

{{1}}

我还没有看到一个变量的真实世界的例子,这个变量并不像通过分离关注点那样无法解决。

答案 1 :(得分:8)

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

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

答案 2 :(得分:7)

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

答案 3 :(得分: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();
}

答案 4 :(得分:3)

我也考虑过这一点。但是,恕我直言,它会造成很多混乱,这将超过它的好处。想想看,C ++中constness的整个概念已经足够令人困惑。

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

答案 5 :(得分: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循环来执行以下语句或仅阻塞一次,其中consoided变量是循环体的本地变量。请注意在本地i_const之前声明辅助变量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,两个版本都有效。所以,是的,非常像你正在寻找的东西确实是可能的,但可能完全不值得。

答案 6 :(得分:3)

我假设你在讨论的东西比仅初始化向量(在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 ++习语。

(根据德曼的建议编辑)

答案 7 :(得分:2)

目前,const或不是编译器知道的内容,因此编译器不会接受尝试更改const变量的程序。

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

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

答案 8 :(得分: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声明相同的范围内定义。

答案 9 :(得分:0)

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