未分配正在释放的 C++ 指针(可能是 unique_ptr 或 boost::ublas 的问题)

C++ Pointer being freed was not allocated (possibly, an issue with unique_ptr or boost::ublas)

这是我之前 questions 之一的后续。我正在处理的问题在上述问题的表述中有详细解释。不幸的是,我无法提供展示问题的最小示例。

在这个问题中,我试图重新定义问题并提供一个最小的例子。下面示例中的代码执行并完成了它应该做的事情。但是,在前面 question 中介绍的稍微复杂的情况下,有时会导致运行时错误

dynamic_links(3941,0x7fff749a2310) malloc: *** error for object 0x61636f6c65720054: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

不幸的是,我只能在优化设置为 -O3(也可能是 -O2)时产生错误。这在使用调试工具时会产生问题。不幸的是,我无法通过 no/minimal 代码优化重现该问题。作为参考,我使用 gcc 4.9.1.

在当前问题中,我想了解我正在使用的 class 继承机制的结构设计从动态内存分配的角度来看是否可能存在危险。请找到以下代码:

namespace ublas = boost::numeric::ublas;

template<typename TScalarType = double>
using ublasRn = ublas::vector<TScalarType>;

class Base
{
public:
    virtual ~Base(void) = 0;
};
Base::~Base(void){}

template<typename T1, typename T2>
class Composite : public Base
{
protected:
    T1 T1Instance;
    std::unique_ptr<T2> u_T2Instance;
public:
    Composite(){}
    virtual ~Composite(void){}
    const std::type_info& returnT1TypeID(void) const
        {return typeid(T1);}
    const std::type_info& returnT2TypeID(void) const
        {return typeid(T2);}
};

template<typename T2>
class CompositeCT: virtual public Composite<double, T2>
{
public:
    using Composite<double, T2>::Composite;
    virtual ~CompositeCT(void)
        {}
};

template<typename T1>
class CompositeRn: virtual public Composite<T1, ublasRn<double>>
{
public:
    using Composite<T1, ublasRn<double>>::Composite;
    virtual ~CompositeRn(void){}
};

class CompositeCTRn :
    public CompositeCT<ublasRn<double>>,
    public CompositeRn<double>
{
public:
    CompositeCTRn(void):
        Composite<double, ublasRn<double>>(),
        CompositeCT<ublasRn<double>>(),
        CompositeRn<double>()
        {};
};

template<typename T1, typename T2, class TComposite>
class Trajectory: public Base
{
protected:
    std::vector<std::unique_ptr<TComposite>> m_Trajectory;
public:
    Trajectory(void)
        {checkType();}
    void checkType(void) const
        {
            TComposite CI;
            if (
                !(
                    CI.returnT1TypeID() == typeid(T1) &&
                    CI.returnT2TypeID() == typeid(T2)
                    )
                )
            throw std::runtime_error("Error");
        }
    virtual ~Trajectory(void){}
};

int main()
{

    Trajectory<
        double,
        ublasRn<>,
        CompositeCTRn
        > T;

    std::cout << 123 << std::endl;
}

注意。我正在使用外部库boost::ublas。我相信问题与 ublas 对象的动态内存分配机制有关的可能性不大。

嗯。我在想你真的在推动类型系统。我无法理解为什么你会想要这种令人震惊的类型层次结构:

在大约 60 行代码中造成了很大的破坏。我想不出 Liskov Substitution Principle 适用于此层次结构的合理情况。

I also note that this kind of emphasis on runtime polymorphism does seem to run counter to the design goals of C++ and uBlas in particular.

尤其是这一行(本质上声明了图表的中心):

class CompositeCTRn : public CompositeCT<ublasRn<double> >, public CompositeRn<double> {

这有效地声明了具有单个虚拟基的 class,即通过三个基的 "aliased":

using VBase  = Composite<double, ublasRn<double> >;
using CTBase = CompositeCT<ublasRn<double> >;
using RnBase = CompositeRn<double>;

这意味着 应该 类型中只有一个 _i1 和一个 _i2 成员。这意味着在所有符合标准的编译器上,以下内容应该编译 运行 而不会触发任何断言或导致内存泄漏等:

class CompositeCTRn : public CompositeCT<ublasRn<double> >, public CompositeRn<double> {
    using VBase  = Composite<double, ublasRn<double> >;
    using CTBase = CompositeCT<ublasRn<double> >;
    using RnBase = CompositeRn<double>;
public:
    CompositeCTRn() : VBase(), CTBase(), RnBase() {
        VBase::_i1 = 1;
        VBase::_i2.reset(new ublasRn<double>(3));

        CTBase::_i1 = 2;
        CTBase::_i2.reset(new ublasRn<double>(3));

        RnBase::_i1 = 3;
        RnBase::_i2.reset(new ublasRn<double>(3));

        assert(3 == VBase::_i1);
        assert(3 == CTBase::_i1);
        assert(3 == RnBase::_i1);

        assert(VBase::_i2.get() == CTBase::_i2.get());
        assert(VBase::_i2.get() == RnBase::_i2.get());

        RnBase::_i2.reset();
        assert(!(VBase::_i2 || CTBase::_i2 || RnBase::_i2));
    };
};

事实上,这正是 gcc 4.8、4.9、5.0 和 clang++ 3.5 上发生的情况。

Live On Coliru

#include <boost/numeric/ublas/vector.hpp>

namespace ublas = boost::numeric::ublas;

template <typename T = double> using ublasRn = ublas::vector<T>;

struct Base {
    virtual ~Base() {}
};

template <typename T1, typename T2> class Composite : public Base {
  protected:
      template <typename, typename, typename> friend class Trajectory; // or make this public
      using Type1 = T1;
      using Type2 = T2;

      Type1 _i1;
      std::unique_ptr<Type2> _i2;

  public:
      Composite() = default;
};

template <typename T2> class CompositeCT : virtual public Composite<double, T2> {
  public:
    using Composite<double, T2>::Composite;
};

template <typename T1> class CompositeRn : virtual public Composite<T1, ublasRn<double> > {
  public:
    using Composite<T1, ublasRn<double> >::Composite;
};

class CompositeCTRn : public CompositeCT<ublasRn<double> >, public CompositeRn<double> {
    using VBase  = Composite<double, ublasRn<double> >;
    using CTBase = CompositeCT<ublasRn<double> >;
    using RnBase = CompositeRn<double>;
  public:
    CompositeCTRn() : VBase(), CTBase(), RnBase() {
        VBase::_i1 = 1;
        VBase::_i2.reset(new ublasRn<double>(3));

        CTBase::_i1 = 2;
        CTBase::_i2.reset(new ublasRn<double>(3));

        RnBase::_i1 = 3;
        RnBase::_i2.reset(new ublasRn<double>(3));

        assert(3 == VBase::_i1);
        assert(3 == CTBase::_i1);
        assert(3 == RnBase::_i1);

        assert(VBase::_i2.get() == CTBase::_i2.get());
        assert(VBase::_i2.get() == RnBase::_i2.get());

        RnBase::_i2.reset();
        assert(!(VBase::_i2 || CTBase::_i2 || RnBase::_i2));
    };
};

template <typename T1, typename T2, class TComposite> class Trajectory : public Base {
    static_assert(std::is_same<T1, typename TComposite::Type1>::value, "mismatched composite");
    static_assert(std::is_same<T2, typename TComposite::Type2>::value, "mismatched composite");
  protected:
    std::vector<std::unique_ptr<TComposite> > m_Trajectory;

  public:
    Trajectory() { checkType(); }
    void checkType() const {
        std::vector<TComposite> CI(1000);
    }
    virtual ~Trajectory() {}
};

#include <iostream>

int main() {
    Trajectory<double, ublasRn<>, CompositeCTRn> T;
    std::cout << 123 << "\n";
}

在 valgrind 下:

==15664== Memcheck, a memory error detector
==15664== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==15664== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==15664== Command: ./test
==15664== 
123
==15664== 
==15664== HEAP SUMMARY:
==15664==     in use at exit: 0 bytes in 0 blocks
==15664==   total heap usage: 6,001 allocs, 6,001 frees, 184,000 bytes allocated
==15664== 
==15664== All heap blocks were freed -- no leaks are possible
==15664== 
==15664== For counts of detected and suppressed errors, rerun with: -v
==15664== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

完全没问题。

备注

  • 我用静态断言替换了类型检查(因为,为什么不呢?!)

    template <typename T1, typename T2, class TComposite> class Trajectory : public Base {
        static_assert(std::is_same<T1, typename TComposite::Type1>::value, "mismatched composite");
        static_assert(std::is_same<T2, typename TComposite::Type2>::value, "mismatched composite");
    

总结/长话短说

我看过你的代码。除了一束漂亮的设计气味外,我真的没有发现任何问题。如果您需要更多帮助,则必须开始具体说明版本、标志、编译器、体系结构...