在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
?)。
答案 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对象