调试实战 | 缺少 const 导致的 bug
前言
最近,在项目里遇到一个很 “诡异” 的问题。明明把后面需要使用的值存起来了,可是使用的时候,却拿到了一堆垃圾数据。有可能是什么原因呢?一起来看看吧。
调查思路
这种问题排查起来比较简单。可以依次排查以下几个地方:
- 保存代码是否正确。
- 读取代码是否正确。
- 在保存后,读取前是否有其它代码修改了保存的值。
按照这个思路,很快定位到问题出在第一步,也就是保存的时候出了问题。
为了让各位小伙伴儿也能实际感受这个问题,我特意模仿实际项目中的代码写了一份可以重现的代码。
关键代码
AdjustInfoByteConverter
里的代码比较简单,而且已经验证过没问题,这就不贴了。感兴趣的小伙伴儿可以下载示例工程查看完整代码。
调用代码如下:
#include "stdafx.h"
#include "DemoObject.h"
int _tmain(int argc, _TCHAR* argv[])
{
CDemoObject object;
AdjustInfo info;
auto bytes = AdjustInfoByteConverter::ToBytes(info);
object.SetAdjustInfo(bytes);
// in real project, object will be serialized, and deserialized.
// following code may run on another thread.
std::vector<byte> restoreBytes;
object.GetAdjustInfo(restoreBytes);
auto info1 = AdjustInfoByteConverter::FromBytes(restoreBytes);
return 0;
}
说明:
在实际排查问题的时候我就是这样缩小排查范围的,调用完
SetAdjustInfo()
,直接调用GetAdjustInfo()
,查看两次结果是否一致。这样可以很快缩小需要排查的范围。在写完
AdjustInfoByteConverter::ToBytes
和AdjustInfoByteConverter::FromBytes
的实现后,也用了类似的办法做了验证,所以在实际项目中直接排除了这两个函数的嫌疑。
CDemoObject
类的实现如下,很规矩。
#pragma once
#include "ObjectProperty.h"
#include "AdjustInfo.h"
#include "AdjustInfoByteConverter.h"
class CDemoObject
{
private:
ExtendPropertySet extendProperty;
public:
void SetAdjustInfo(const std::vector<byte>& bytes)
{
PropertyValue value(bytes);
extendProperty.SetSubParam("AdjustInfo", value);
}
int GetAdjustInfo(std::vector<byte>& bytes) const
{
PropertyValue value;
extendProperty.GetSubParam("AdjustInfo", value);
bytes = value.m_value.m_valueByte;
return (int)bytes.size();
}
};
有瑕疵的代码如下,但并不是所有情况下都有问题,你能找出这个问题吗?
#pragma once
#include <vector>
#include <map>
class PropertyValue
{
public:
enum { VALUENULL, INT, DOUBLE, Bool, String, Block } m_type;
PropertyValue() : m_type(VALUENULL) {}
struct
{
std::vector<byte> m_valueByte;
} m_value;
template<class T>
PropertyValue(T value)
{
m_type = Block;
unsigned __int32 nSize = sizeof(value);
byte *data = (byte*)&value;
for (unsigned __int32 i = 0; i < nSize; ++i)
{
m_value.m_valueByte.push_back((byte)(*(data + i)));
}
}
PropertyValue(std::vector<byte>& value)
{
m_type = Block;
m_value.m_valueByte = value;
}
};
class ExtendPropertySet
{
public:
void SetSubParam(const std::string& name, PropertyValue param)
{
m_SubParamMap[name] = param;
}
bool GetSubParam(const std::string& name, PropertyValue& param) const
{
auto it = m_SubParamMap.find(name);
if (it != m_SubParamMap.end())
{
param = it->second;
return true;
}
return false;
}
private:
std::map<std::string, PropertyValue> m_SubParamMap;
};
根本原因
这个问题的根本原因在于:调用了错误的 PropertyValue
构造函数。
预期被调用的函数是 PropertyValue(std::vector<byte>& value)
,而实际调用的函数却是 template<class T> PropertyValue(T value)
。
因为 CDemoObject
类的 void SetAdjustInfo(const std::vector<byte>& bytes)
函数的参数是 const
的。在编译 PropertyValue value(bytes);
这行代码的时候,需要找到一个最优的构造函数,最终找到的是 template
版本的。不能把一个 const
对象丢给一个参数是非 const
的函数!
解决方案
这个问题解决起来很简单,有两种改法:
- 去掉
const
对象的const
属性。 - 改动底层代码,把非
const
版本改成const
版本的函数。
实际项目中采用的第一种改法,因为没有权限改动底层接口,但这种改法治标不治本。
下载链接
百度云盘链接: 链接: https://pan.baidu.com/s/1a2p9YWPLtlOe6dM_j_s80w 提取码: j96h
CSDN:https://download.csdn.net/download/xiaoyanilw/14965002
总结
- 尽量使用引用传递类对象,而且如果不想在函数内部修改这个对象的话,务必加上
const
。 - 不能把一个
const
对象丢给一个非const
参数的函数。 - 排查问题的时候,尽可能的缩小范围。