[C/C++安全编程]_[中级]_[如何实现不可变变量]

场景

  1. Rust里有不可变变量,不可变变量可以保证编译器内存安全,禁止数据竞争;并且不可变可以安全的跨线程共享,无需锁。那么C/C++对象有这种不可变变量吗?

说明

  1. 首先说下简单类型是可以通过const来修饰不可变特性的。

  2. 对象类型结构的不可变特性。先说C肯定是没有的,C的结构体都是public结构,想要让成员不可变,只能通过const来修饰成员变量,但是如果修饰了,也不能改了,虽然可以通过const_cast<int*>运算符来移除const,但是得到的变量如果想修改,结果是未定义的。这个运算符是为了兼容旧代码用的,现代编程这个运算符没用。

  3. C++对象在严格的声明成员变量结构时是可以做到的rust不可变变量的效果。 它的成员变量需要声明在private里,之后声明publicget,set方法。注意get方法必须声明const,这样可以让const对象和非const对象调用。而不要声明constset方法,在const调用时会编译报错。如:编译错误,setValue不支持const Base类型。这样声明的方式就是防止成员变量在const对象时被直接修改,因此在有const对象时,只能调用get方法,不然会编译报错。

class Base
{
public:
	Base() {
	
	}

	Base(const Base& base) {
		value_ = base.value_;
		str_ = base.str_;
	}
	
	Base(Base&& base) {
		value_ = base.value_;
		str_ = move(base.str_);

		base.value_ = 0;
	}
	
	void setValue(int value) {
		value_ = value;
	}

	int getValue() const {
		return value_;
	}

	void setStr(const string& str) {
		str_ = str;
	}

	const string& getStr() const{
		return str_;
	}

private:

	string str_;

	int value_ = 100;

};
  1. 如果 const 变量指向可变数据(如 const T* 指向非 const 数据),可能引发竞态条件。 所以const不要指向非const对象。如果一个非const想转换为const,那么可以用std::move直接把所有权移动给const对象,原本的对象数据也就失效了。
Base base3;
base3.setValue(101);
base3.setStr("hello");
const Base base5 = move(base3);

  1. 除了以上说的严谨声明类结构,C++还有其他常量限制的特性。如常量指针,指针常量和constexpr
  • 常量指针,初始化后不可修改指针指向的值;指针地址可变;
  • 指针常量, 指针地址不可变;指针指向的值可改;
  • 编译器常量,可以计算出简单语句

例子

#include <iostream>
#include <string>
#include <sstream>
#include <thread>
#include <vector>
#include <functional>
#include <type_traits>

using namespace std;

//template<class T>
//static bool IsConst(T t) {
//	return is_const<decltype(t)>();
//}

template<class T>
static bool IsConstV(T& t) {
   return is_const<std::remove_reference_t<decltype(t)>>();
}

template<class T>
static bool IsConstV(T* t) {
   return is_const<std::remove_pointer_t<decltype(t)>>();
}

class Base
{
public:
   Base() {
   
   }

   Base(const Base& base) {
   	value_ = base.value_;
   	str_ = base.str_;
   }
   
   Base(Base&& base) {
   	value_ = base.value_;
   	str_ = move(base.str_);

   	base.value_ = 0;
   }
   
   void setValue(int value) {
   	value_ = value;
   }

   int getValue() const {
   	return value_;
   }

   void setStr(const string& str) {
   	str_ = str;
   }

   const string& getStr() const{
   	return str_;
   }

private:

   string str_;

   int value_ = 100;

};

void TestConstPointerConst()
{
   // 常量指针,初始化后不可修改指针指向的值;指针地址可变;
   int number = 100;
   const int *pNumber = &number;
   //*pNumber = 101; // 编译错误,表达式必须是可修改的左值

   cout << "pNumber: " << *pNumber << endl;

   // 指针常量, 指针地址不可变;指针指向的值可改
   int *const pNumber2 = &number;
   *pNumber2 = 101;
   cout << "number: " << number << endl;
   //pNumber2 = NULL; // 表达式必须是可修改的左值

   // 编译器常量,可以计算出简单语句
   // 256
   constexpr int64_t offset = 1 << 8;
   cout << "offset: " << offset << endl;
}

void Add(const string& str)
{
   const int i = 9;
   using myType = decltype(str);
   using myType2 = decltype(i);

   const string* p = new string("hello");

   cout << IsConstV(i) << endl;
   cout << IsConstV(str) << endl;
   cout << IsConstV(p) << endl;

   string str2;
   string& str3 = str2;

   string* p2 = &str2;

   cout << "===================" << endl;
   cout << IsConstV(str2) << endl;
   cout << IsConstV(str3) << endl;
   cout << IsConstV(p2) << endl;

   cout << "===================" << endl;
   const Base* base = new Base();
   Base* base2 = new Base();
   cout << base->getValue() << endl;
   cout << base2->getValue() << endl;
   delete base;
   delete base2;

   ////base->setValue(100); // 编译错误,setValue不支持const Base类型
   Base base3;
   base3.setValue(101);

   const Base base4;
   //base4.setValue(102); // 编译错误,setValue不支持const Base类型


   const string str23;
   //str23.append("hello"); // 编译错误, 
   cout << str23 << endl;

   // 1. 如果要多线程共享读取,那么把变量移动给新的不可变量。
   //    如果没有重载Base(Base&& base)构造函数,那么只会是复制数据。
   base3.setStr("hello");
   const Base base5 = move(base3);
   cout << "base5 address: " << (int)&base5 << endl;
   
   // 不可变变量,可以多线程访问。
   vector<thread> ts;
   for (int i = 0; i < 3; ++i) {
   	ts.emplace_back(bind([](const Base& base) {
   		stringstream ss;

   		ss << "base address: " << (int)&base << "\n";
   		ss << "Thread: ->" << base.getValue() << "\n";
   		ss << "Thread: ->" << base.getStr() << "\n";
   		//base.setValue(101); // 编译错误,setValue不支持const Base类型

   		cout << ss.str() << endl;
   	},std::ref(base5)));
   }
   
   for (int i = 0; i < ts.size(); ++i)
   	ts.at(i).join();
}

int main()
{
   std::cout << "Hello World!\n";

   std::string str("Peter");
   Add(str);
   TestConstPointerConst();
}

输出

Hello World!
1
1
1
===================
0
0
0
===================
100
100

base5 address: 10418828
base address: 10418828
Thread: ->101
Thread: ->hello

base address: 10418828
Thread: ->101
Thread: ->hello

base address: 10418828
Thread: ->101
Thread: ->hello

pNumber: 100
number: 101
offset: 256

参考

  1. 如何避免出现野指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值