具有 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 是不同的。结果如下:

如您所见,v1v2 具有相同的 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);,因为bsDatam_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++ 类对象具有奇怪的行为的主要内容,如果未能解决你的问题,请参考以下文章

COleVariant类

如何创建只有具有管理员和管理频道权限的成员才能使用的“创建文本/vc 频道”命令?

如何在 vc 6.0 中将对象转换或分配为 Variant 类型

如何复制动态分配的对象(具有一个类的 const 成员)

类的基础

当元素具有类成员 Boost Concurrent Queue 时无法调整向量的大小