具有 VARIANT 成员的 VC++ 类对象具有奇怪的行为
Posted
技术标签:
【中文标题】具有 VARIANT 成员的 VC++ 类对象具有奇怪的行为【英文标题】:VC++ class objects with a VARIANT member have strange behavior 【发布时间】:2016-10-20 01:13:02 【问题描述】:我在下面定义了一个类:
class CVariable
public:
CVariable(CString strData, int nNum);
CVariable(BSTR bsData);
~CVariable();
public:
VARIANT GetVariant()return m_bsVa;;
private:
VARIANT m_bsVa;
VARIANT m_nVa;
;
而实现是:
CVariable::CVariable(CString strData, int nNum)
VariantInit(&m_bsVa);
BSTR bsData = ::SysAllocString(strData);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
::SysFreeString(bsData);
VariantInit(&m_nVa);
m_nVa.vt = VT_I2;
m_nVa.lVal = nNum;
CVariable::CVariable(BSTR bsData)
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
CVariable::~CVariable()
VariantClear(&m_bsVa);
VariantClear(&m_nVa);
当我尝试使用构造函数CVariable(CString,int)
构造两个实例时,
类成员 m_bsVas 总是有相同的值,而 m_nVas 是不同的。结果如下:
如您所见,v1 和 v2 具有相同的 m_bsVa 但不同的 m_nVa,而使用构造函数 CVariable(BSTR)
会导致正确的结果。我不知道为什么会发生这种情况?
任何帮助将不胜感激。
【问题讨论】:
【参考方案1】:我发现您的代码存在一些问题。
CVariable(CString, int)
构造函数为m_bsVa
分配一个BSTR
,但随后立即释放BSTR
,使m_bsVa
指向无效内存,并允许下一个CVariable
实例可能重用分配的BSTR
的内存地址相同。您需要保留分配的BSTR
,直到您完成使用m_bsVa
(或至少直到您想要为其分配新值)。 VariantClear()
将为您释放BSTR
。
CVariable(BSTR)
构造函数根本没有初始化m_nVa
,这会导致后续操作出现问题,包括VariantClear()
。此外,构造函数正在获取调用者的BSTR
的所有权。这可能会也可能不会,这取决于您如何使用此构造函数。如果调用者不希望您获得所有权,那么您需要使用SysAllocString/Len()
复制BSTR
。
VARIANT
不可轻易复制。您需要使用VariantCopy()
函数将数据从一个VARIANT
复制到另一个。这意味着您的 CVariable
类需要实现复制构造函数和复制赋值运算符。无论如何您都需要这样做,以便您的课程符合Rule of Three。
GetVariant()
原样返回m_bsVa
,因此编译器将简单地将m_bsVa
的字段的值原样复制到调用者的接收VARIANT
中。由于BSTR
是一个指针,调用者将直接访问到您类中的原始BSTR
。这可能会也可能不会,这取决于您如何使用GetVariant()
。在当前实现中,对返回的 BSTR
的任何访问都应被视为只读 - 调用者不得对其调用 SysFreeString()
,并且必须预期对 CVariable
对象的任何更改可能使BSTR
无效。如果这不符合您的需求,那么GetVariant()
应该返回一个新的VARIANT
,该VARIANT
已经通过VariantCopy()
复制了数据,然后调用者可以在使用返回的VARIANT
完成后调用VariantClear()
。
话虽如此,请尝试更多类似的东西:
class CVariable
public:
CVariable(const CString &strData, int nNum);
CVariable(BSTR bsData);
CVariable(const CVariable &src);
~CVariable();
VARIANT GetVariant() const;
CVariable& operator=(const CVariable &src);
CVariable& operator=(BSTR src);
private:
VARIANT m_bsVa;
VARIANT m_nVa;
;
CVariable::CVariable(const CString &strData, int nNum)
::VariantInit(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = ::SysAllocString(strData);
::VariantInit(&m_nVa);
m_nVa.vt = VT_I2;
m_nVa.lVal = nNum;
CVariable::CVariable(BSTR bsData)
::VariantInit(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
/* or this, if needed:
m_bsVa.bstrVal = ::SysAllocStringLen(bsData, ::SysStringLen(bsData));
*/
::VariantInit(&m_nVa);
CVariable::~CVariable()
::VariantClear(&m_bsVa);
::VariantClear(&m_nVa);
VARIANT CVariable::GetVariant() const
return m_bsVa;
/* or this, if needed:
VARIANT result;
::VariantInit(&result);
::VariantCopy(&result, &m_bsVa);
return result;
*/
CVariable& CVariable::operator=(const CVariable &src)
if (&src != this)
::VariantClear(&m_bsVa);
::VariantCopy(&m_bsVa, &src.m_bsVa);
::VariantClear(&m_nVa);
::VariantCopy(&m_nVa, &src.m_nVa);
return *this;
CVariable& CVariable::operator=(BSTR src)
::VariantClear(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = src;
/* or this, if needed:
m_bsVa.bstrVal = ::SysAllocStringLen(src, ::SysStringLen(src));
*/
::VariantClear(&m_nVa);
return *this;
如果您直接使用variant_t
类而不是VARIANT
,则可以大大简化代码,同时仍然解决上述所有问题:
class CVariable
public:
CVariable(const CString &strData, int nNum);
CVariable(BSTR bsData);
variant_t GetVariant() const;
private:
variant_t m_bsVa;
variant_t m_nVa;
;
CVariable::CVariable(const CString &strData, int nNum)
: m_bsVa(strData), m_nVa(nNum)
CVariable::CVariable(BSTR bsData)
: m_bsVa(bsData)
variant_t CVariable::GetVariant() const
return m_bsVa;
【讨论】:
【参考方案2】:在这个构造函数中:
CVariable::CVariable(BSTR bsData)
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
您将离开 m_nVa
未初始化 - 它会获得一些随机值。它应该看起来像这样:
CVariable::CVariable(BSTR bsData)
VariantInit(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
VariantInit(&m_nVa);
在这个构造函数中:
CVariable::CVariable(CString strData, int nNum)
VariantInit(&m_bsVa);
BSTR bsData = ::SysAllocString(strData);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
::SysFreeString(bsData);
VariantInit(&m_nVa);
m_nVa.vt = VT_I2;
m_nVa.lVal = nNum;
不要调用::SysFreeString(bsData);
,因为bsData
归m_bsVa
所有。
SysFreeString()
释放内存,下一次SysAllocString()
调用可能会在同一内存地址创建一个新的BSTR
字符串。
我建议您使用_variant_t
class,而不是使用naked VARIANT。在这种情况下,您根本不需要担心VariantInit()
/VariantClear()
,因为它为您实现了 C++ 风格的所有权策略。
【讨论】:
我忘记了。但是我想知道为什么使用CVariable(CString, int)
会导致相同的 m_bsVa 值和不同的 m_nVa 值。
我已经更新了我的答案,从 ctor 中删除了 ::SysFreeString(bsData);
。我强烈建议改用_variant_t
,否则你会非常了解VARIANT的所有权政策。【参考方案3】:
我建议您对原始 C 变体使用方便的 C++ RAII 包装器,例如来自 ATL 的 CComVariant。
这将简化您的代码,因为 CComVariant 将正确初始化其包装的原始 VARIANT,并清理它。
您可以用更安全的 CComVariant 包装器替换您的 VARIANT 数据成员:
CComVariant m_bsVa;
CComVariant m_nVa;
然后你可以像这样在构造函数中初始化它们:
CVariable::CVariable(const CString& strData, int nNum)
: m_bsVa(strData), m_nVa(nNum)
CVariable::CVariable(BSTR bsData)
: m_bsVa(bsData)
请注意,您不需要显式定义析构函数,因为在这种情况下,CComVariant 的析构函数会正确清理数据成员。
你的 getter 可以这样实现:
const CComVariant& CVariable::GetVariant() const
return m_bsVa;
【讨论】:
以上是关于具有 VARIANT 成员的 VC++ 类对象具有奇怪的行为的主要内容,如果未能解决你的问题,请参考以下文章
如何创建只有具有管理员和管理频道权限的成员才能使用的“创建文本/vc 频道”命令?