我刚刚发现在没有任何const_cast
黑魔法的情况下修改const对象是多么容易。考虑:
#include <iostream>
class Test {
public:
Test(int v)
:m_val{ v },
m_ptr{ &m_val }
{}
int get() const { return m_val; }
void set(int v) const { *m_ptr = v; }
private:
int m_val;
int* m_ptr;
};
int main()
{
const Test t{ 10 };
std::cout << t.get() << '\n';
t.set(0);
std::cout << t.get() << '\n';
return 0;
}
最新版本的Clang,GCC和MSVC不会显示任何警告并产生预期输出:
10 0
这是根据现行标准定义良好的行为吗?如果未定义m_val
类型std::aligned_storage_t<sizeof(int), alignof(int)>
和构造函数new
&#39; ed int
中的内容是什么?我相信在小缓冲区优化方面这是非常常见的情况。
修改
谢谢,这似乎只是另一种用脚射击自己的方式。 这似乎令人不安的是:
struct Test2 {
int i;
void operator()() { ++i; }
};
const std::function<void()> f{ Test2{ 10 } };
f();
当实现选择将Test2
对象存储在f
内时,也是未定义的行为(在libc ++和Visual Studio中就是这种情况)
答案 0 :(得分:7)
const
强制执行&#34;按位常量&#34;,但你通常想要的是&#34;逻辑常量&#34;。
对于包含指针的对象,这意味着const成员函数不能修改指针本身,但可以修改指针引用的内容。
这在很长一段时间内都是众所周知的。
要获得逻辑常量,您1)使用mutable
(或有时const_cast
)来修改不会影响对象逻辑状态的成员(例如,缓存值/ memoization),2)通常必须手动强制不通过指针写入数据(但如果它是拥有指针,那么所有权可能应该委托给仅的对象管理该数据的所有权,在这种情况下,使其成为常常应该阻止写入它拥有的数据。
至于具有指向可能本身已被修改的数据的非const指针的具体细节,那么,你基本上只是获得与{{大致相同的东西的(持久)版本。 1}}通常用于:获取非const访问数据,否则您只有const_cast
指针。您可以确保只以不会导致问题的方式使用它(但只是通过指针进行和/或写入本身不一定会导致问题。问题)。
换句话说,我们这里有两个单独指向某些数据的指针。 const
可让您访问对象的数据。在this
成员函数中,您只能通过const
读取(不)写入数据,除非(如上所述)它标记为this
。在这种情况下,您将保存指向同一数据的第二个指针。由于没有任何内容可以将其标记为指向mutable
的指针,因此不会,因此您可以获得对其指向的数据的非const访问权。
答案 1 :(得分:4)
正如其他人在评论中指出的那样:您正在修改m_ptr
指向的对象。这&#34;指向&#34; object不是class Test
的一部分(就编译器而言)。这就是编译器允许你这样做的原因。
话虽如此,我相信这将是未定义的行为。这是因为m_ptr
实际指向对象m_val
的另一个成员变量(const Test t
)!允许编译器进行arggresively优化,并且可能依赖constness来实现。
唯一的例外是您使用mutable
关键字,但它是另一个故事。
答案 2 :(得分:2)
C ++ 中基本上有两种类型的const:物理常量和逻辑常量。
至于物理常量,所有代码在所考虑的代码段中都是完全有效的,因为set()
修改了由m_ptr
指向的值而不是指针本身,它是类的一部分。< / p>
这里违反了逻辑常量。但是 C ++ 中有许多方法违反逻辑const,因为这种类型的const依赖于特定的类设计。
在上面的示例中,程序指向UB,因为它尝试更改const对象。
来自n4296,7.1.6.1 cv-qualifiers :
除了可以修改声明为mutable(7.1.1)的任何类成员, 任何在其生命周期(3.8)结果中修改const对象的尝试 在未定义的行为中。
答案 3 :(得分:1)
这是未定义的行为。并非所有const
声明的类型都是常量,因此修改以这种方式声明的内容并不总是未定义的行为。您可以引用一个引用非const非const值的const类型,抛弃constness并修改该值而不调用未定义的行为。在这种情况下,虽然原始定义是const
所以你必须假设它是一个常数。
任何常量的修改都是未定义的行为,是的,有很多方法可以“意外地”这样做。在aligned_storage版本中,是的,通过使用placement new来修改常量数据是未定义的行为。