记C++坑:2.局部变量的申明方式

最近有同事问,为什么他写的下面这段代码会报错:
int cls::getmapvalwithlock(int key, std::mutex& lock)
{
	std::unique_lock<std::mutex>(lock);
	return m_map[key];
}

错误原因是变量lock重定义。

然后他将代码改成

int cls::getmapvalwithlock(int key, std::mutex& lock)
{
	{
		std::unique_lock<std::mutex>(lock);
		return m_map[key];
	}
}
就避免了错误,但是这样真的能够加锁成功吗?

再看下面的代码:

#include <iostream>

class cls_ii;

class cls_i
{
public:
	operator cls_ii();
};

class cls_ii
{
public:
	cls_ii()
	{
		std::cout << "constructor(default)" << std::endl;
	}
	explicit cls_ii(cls_i& c)
		: m_c(c)
	{
		std::cout << "constructor(cls_i)" << std::endl;
	}
	explicit cls_ii(int i)
		: m_i(i)
	{
		std::cout << "constructor(int)" << std::endl;
	}
	~cls_ii()
	{
		std::cout << "destructor" << std::endl;
	}
private:
	cls_i m_c;
	int   m_i;
};

cls_i::operator cls_ii()
{
	std::cout << "cls_i::operator cls_ii()" << std::endl;
	int i = 3;
 	return cls_ii(i);
}

void funct(cls_i& c, int i)
{
	{
		cls_ii (c);
	}
	{
		cls_ii(cls_i());
	}
	{
		cls_ii (i);
	}
	{
		cls_ii (2);
	}
}

void test_constructor()
{
	cls_i c;
	funct(c, 1);
}

int main()
{
	test_constructor();
	return 1;
}
输出应该是什么呢?

注意funct函数,如果每一句不加花括号的话,就会收到和最开始那段代码同样的错误,同时,注意到

cls_ii(cls_i());
其实并没有执行,并且这一行编译的时候可能收到一个警告,大致意思就是问你为什么申明了一个函数签名,但是又没有调用,你丫想干嘛。

那么既然怪咱没有调用,那调用一下试试看咯。代码改成:

	{
		cls_ii(cls_i());
		cls_i();
	}
编译没有问题了,但是链接出错,会不会纳闷为什么上面申明了cls_i类,这里的
cls_i();
却不是如自己预想的调用了类的构造函数申明一个没有名字的临时变量?

上面的两个问题其实原因很简单,然而有时候可能越是简单的问题越是因为容易忽略而被坑

首先,

cls_ii (c);

这一系列的语句没有调用预想的带参构造函数是因为

cls_ii (c);
cls_ii c;

这两句代码的意思是一致的,这也就解释了为什么这样的语句需要用花括号括起来,不然就会报错,提示变量名和参数名重复。

而为什么

cls_ii (1);

却调用了int参数的构造函数,是因为扩号中是个字面值常量,当然不可能是标识符了,但是至于为什么编译器会将

cls_ii (X);//X可能是标识符也可能是字面值

解释成不同的结果到底是语言特性导致的优先级问题还是编译器处理逻辑所致还没有研究过,不是很清楚,还希望大神不吝赐教。

其次,为什么

cls_ii(cls_i());

这一句没有执行,但从编译警告就可以看出,编译器认为你只是在这里声明了一个函数签名,这样的使用有很多,比较常见的是动态加载链接库的时候需要通过函数名查找需要调用的函数地址,然后强转成声明的签名形式。

所以,既然是一个函数形式申明,当然不会调用咯,改成下面的代码,就可以正确执行了,

cls_ii(*(new cls_i()));

但是要注意存在内存泄漏。

那么所以为什么接下来直接调用

cls_i();

也会出问题,更简单,因为当前局部的cls_i(函数)声明覆盖了代码块外部的cls_i(类)申明,上面的代码其实是在调用一个只有申明但是没有实现的函数,那么编译当前文件没有问题,但是当链接的时候,编译器发现找不到这个函数的定义,自然就出错了。


这个代码例子中还有其他一些细节,也一并写出来一下,当做复习:

关于类型转换运算符和不同类型单参数的构造函数在隐式类型转换中的优先级问题,以及explicit关键字的作用。

假如有如下代码

cls_i i;
cls_ii ii = i;

现有的代码调用的是cls_i的类型转换运算符重载;

如果去掉cls_ii的cls_i类型参数的构造函数的explicit修饰限制,调用的是该构造函数;

如果保留explicit,去掉类cls_i的类型转换运算符重载,则编译出错,因为explicit的作用就是保证构造函数必须显示调用,避免敲代码的笔误造成的不必要错误。


所以回到正题,最开始的加锁代码,是不能成功加锁的,需要改成

std::unique_lock<std::mutex> l(lock);

附上完整的测试代码,用事实说话

#include <thread>
#include <future>
#include <mutex>
#include <list>

// #define type_wrong

#ifdef type_wrong
#define LOCK std::unique_lock<std::mutex> (m_lock_lst);
#else
#define LOCK std::unique_lock<std::mutex> lock(m_lock_lst);
#endif

class test_lock
{
public:
	test_lock()
	{
		srand(time(NULL));
		m_lst_.push_back(1);
	}
	void ergodic_lst()
	{
		LOCK
		int i = 0;
		for (auto it = m_lst_.begin(); it != m_lst_.end(); ++it)
		{
			++i;
		}
	}
	void insert_lst()
	{
		LOCK
		int times = rand() % m_lst_.size();
		auto it = m_lst_.begin();
		for (int i = 0; i < times && it != m_lst_.end(); ++i,++it);
		m_lst_.insert(it, rand() % 1000);
	}
	void erase_lst()
	{
		LOCK
		if (m_lst_.size() > 1)
		{
			int times = rand() % m_lst_.size();
			auto it = m_lst_.begin();
			for (int i = 0; i < times && it != m_lst_.end(); ++i, ++it);
			m_lst_.erase(it);
		}
	}

private:
	std::mutex m_lock_lst;
	std::list<int>	m_lst_;
};

int threadfunc1(test_lock* cls)
{
	while (1)
	{
		cls->ergodic_lst();
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}
	return 1;
}

int threadfunc2(test_lock* cls)
{
	while (1)
	{
		cls->insert_lst();
		std::this_thread::sleep_for(std::chrono::milliseconds(13));
	}
	return 1;
}


int threadfunc3(test_lock* cls)
{
	while (1)
	{
		cls->erase_lst();
		std::this_thread::sleep_for(std::chrono::milliseconds(59));
	}
	return 1;
}

void test_async()
{
	test_lock tl;
	std::thread t1(std::packaged_task<int(test_lock*)>(threadfunc1), &tl);
	std::thread t2(std::packaged_task<int(test_lock*)>(threadfunc2), &tl);
	std::thread t3(std::packaged_task<int(test_lock*)>(threadfunc3), &tl);
	t1.join();
	t2.join();
	t3.join();
}

int main()
{
	test_async();
	return 1;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值