VC++ class 具有 VARIANT 成员的对象有奇怪的行为
VC++ class objects with a VARIANT member have strange behavior
我有一个 class 定义如下:
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)
构造两个实例时,
class成员m_bsVa总是有相同的值,而m_nVa是different.The 结果如下:
如您所见,v1 和 v2 具有相同的 m_bsVa 但不同的 m_nVa,同时使用constructor CVariable(BSTR)
导致正确的结果。我不知道为什么会这样?
任何帮助将不胜感激。
在这个构造函数中:
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
字符串。
我建议您不要使用 naked VARIANT,而是使用 _variant_t
class。在这种情况下,您根本不需要担心 VariantInit()
/VariantClear()
,因为它以 C++ 风格为您实现了所有权策略。
我发现你的代码有几个问题。
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
class 需要实现复制构造函数和复制赋值运算符。无论如何你都需要这样做,这样你的 class 就符合 Rule of Three.
GetVariant()
按原样 returning m_bsVa
,因此编译器将简单地复制 m_bsVa
字段的值作为-进入来电接收VARIANT
。由于 BSTR
是一个指针,调用者将 直接访问 到您的 class 中的原始 BSTR
。这可能会或可能不会,取决于您如何使用 GetVariant()
。在当前的实现中,对 returned BSTR
的任何访问都应被视为只读 - 调用者 不得 在其上调用 SysFreeString()
,并且必须预料到 CVariable
对象的任何更改 可能 会使 BSTR
无效。如果这不符合您的需要,那么 GetVariant()
应该 return 一个新的 VARIANT
已经通过 VariantCopy()
复制了数据,然后调用者可以调用 VariantClear()
使用 returned VARIANT
.
完成后
话虽如此,试试这样的东西:
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
class 而不是 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;
}
我建议您在原始 C VARIANT 周围使用方便的 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;
}
我有一个 class 定义如下:
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)
构造两个实例时,
class成员m_bsVa总是有相同的值,而m_nVa是different.The 结果如下:
如您所见,v1 和 v2 具有相同的 m_bsVa 但不同的 m_nVa,同时使用constructor CVariable(BSTR)
导致正确的结果。我不知道为什么会这样?
任何帮助将不胜感激。
在这个构造函数中:
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
字符串。
我建议您不要使用 naked VARIANT,而是使用 _variant_t
class。在这种情况下,您根本不需要担心 VariantInit()
/VariantClear()
,因为它以 C++ 风格为您实现了所有权策略。
我发现你的代码有几个问题。
CVariable(CString, int)
构造函数为m_bsVa
分配了一个BSTR
,但随后立即释放了BSTR
,留下m_bsVa
指向无效内存,并允许下一个CVariable
实例可能为其分配的BSTR
重用相同的内存地址。您需要保留分配的BSTR
直到您使用m_bsVa
完成(或者至少直到您想要为其分配一个新值)。VariantClear()
将为您免费BSTR
。CVariable(BSTR)
构造函数根本没有初始化m_nVa
,这会导致后续操作出现问题,包括VariantClear()
。此外,构造函数正在取得调用者BSTR
的所有权。这可能可行也可能不可行,具体取决于您如何使用此构造函数。如果来电者不希望您获得所有权,那么您需要使用SysAllocString/Len()
. 复制 VARIANT
不可简单复制。您需要使用VariantCopy()
函数将数据从一个VARIANT
复制到另一个。这意味着您的CVariable
class 需要实现复制构造函数和复制赋值运算符。无论如何你都需要这样做,这样你的 class 就符合 Rule of Three.GetVariant()
按原样 returningm_bsVa
,因此编译器将简单地复制m_bsVa
字段的值作为-进入来电接收VARIANT
。由于BSTR
是一个指针,调用者将 直接访问 到您的 class 中的原始BSTR
。这可能会或可能不会,取决于您如何使用GetVariant()
。在当前的实现中,对 returnedBSTR
的任何访问都应被视为只读 - 调用者 不得 在其上调用SysFreeString()
,并且必须预料到CVariable
对象的任何更改 可能 会使BSTR
无效。如果这不符合您的需要,那么GetVariant()
应该 return 一个新的VARIANT
已经通过VariantCopy()
复制了数据,然后调用者可以调用VariantClear()
使用 returnedVARIANT
. 完成后
BSTR
话虽如此,试试这样的东西:
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
class 而不是 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;
}
我建议您在原始 C VARIANT 周围使用方便的 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;
}