调试实战 | 缺少 const 导致的 bug

前言

最近,在项目里遇到一个很 “诡异” 的问题。明明把后面需要使用的值存起来了,可是使用的时候,却拿到了一堆垃圾数据。有可能是什么原因呢?一起来看看吧。

调查思路

这种问题排查起来比较简单。可以依次排查以下几个地方:

  1. 保存代码是否正确。
  2. 读取代码是否正确。
  3. 在保存后,读取前是否有其它代码修改了保存的值。

按照这个思路,很快定位到问题出在第一步,也就是保存的时候出了问题。

为了让各位小伙伴儿也能实际感受这个问题,我特意模仿实际项目中的代码写了一份可以重现的代码。

关键代码

AdjustInfoByteConverter 里的代码比较简单,而且已经验证过没问题,这就不贴了。感兴趣的小伙伴儿可以下载示例工程查看完整代码。

调用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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::ToBytesAdjustInfoByteConverter::FromBytes 的实现后,也用了类似的办法做了验证,所以在实际项目中直接排除了这两个函数的嫌疑。

CDemoObject 类的实现如下,很规矩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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();
}
};

有瑕疵的代码如下,但并不是所有情况下都有问题,你能找出这个问题吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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 的函数!

解决方案

这个问题解决起来很简单,有两种改法:

  1. 去掉 const 对象的 const属性。
  2. 改动底层代码,把非 const 版本改成 const 版本的函数。

实际项目中采用的第一种改法,因为没有权限改动底层接口,但这种改法治标不治本。

下载链接

百度云盘链接: 链接: https://pan.baidu.com/s/1a2p9YWPLtlOe6dM_j_s80w 提取码: j96h

CSDN:https://download.csdn.net/download/xiaoyanilw/14965002

总结

  • 尽量使用引用传递类对象,而且如果不想在函数内部修改这个对象的话,务必加上 const
  • 不能把一个 const 对象丢给一个非 const 参数的函数。
  • 排查问题的时候,尽可能的缩小范围。
BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
  • 本文作者: BianChengNan
  • 本文链接: https://bianchengnan.gitee.io/articles/use-const-if-possible/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!
  • 作者寄语: 文章的结束只是思考的开始,您宝贵的意见和建议将是我继续前行的动力,点击右侧分享按钮即可携友同行!
0%