boost :: align_storage копирование объектов NON-POD, например POD-типа, по возвращении - PullRequest
1 голос
/ 13 октября 2011

У меня есть класс, использующий хранилище boost :: align для статического выделения памяти внутри объекта, а затем инициализации объектов в более позднее время. Однако происходит сбой в одной тестовой программе, который показывает деструкторы членов, вызываемые для инициализированного объекта, в данном случае std :: set, когда я пытаюсь вернуть объект после вызова функции. Затем, по-видимому, копируются только данные при присвоении новому объекту на сайте возврата, как если бы этот объект имел тип POD. Это означает, что в следующий раз, когда я попытаюсь получить доступ к назначенному объекту, произойдет сбой с ошибкой доступа к памяти.

Более конкретно, я реализую класс с именем StaticVector, подмножество std :: vector, которое пытается близко соответствовать Boost.Array плюс отслеживание размера и фиксированная емкость во время компиляции.

Моя тестовая программа benchStaticVector работает следующим образом:

  1. вызывает функцию, которая добавляет случайно заполненный std :: set в StaticVector
  2. возвращает его в локальную переменную
  3. деструктор вызывается при возврате, но по какой-то причине возвращаемый объект не копируется правильно
  4. когда локальная переменная выходит из области видимости, вызывается неинициализированная память
  5. Крах

Каков источник этого сбоя и как его устранить?

Полный код и удобный файл CMakeLists.txt можно найти по адресу: https://github.com/ahundt/Boost.StaticVector

benchStaticVector.cpp:

// benchmark based on: http://cpp-next.com/archive/2010/10/howards-stl-move-semantics-benchmark/

#include "StaticVector.hpp"
#include <vector>
#include <iostream>
#include <time.h>
#include <set>
#include <algorithm>
#include <exception>

const unsigned N = 3;
extern bool some_test;

template<typename T>
T get_set(std::size_t)
{
    T s;
    for (std::size_t i = 0; i < N; ++i)
        while (!s.insert(std::rand()).second)
            ;
    if (some_test)
        return s;
    return T();
}

template<typename T>
T generate()
{
    T v;
    for (std::size_t i = 0; i < N; ++i)
        v.push_back(get_set<typename T::value_type>(i));
    if (some_test)
        return v;
    return T();
}

template<typename T>
float time_it()
{
    clock_t t1, t2, t3, t4;
    clock_t t0 = clock();
    {
    T v(generate<T>());
    }
    return (float)((t4-t0)/(double)CLOCKS_PER_SEC);
}

int main()
{
  try {
    std::cout << "N = " << N << "\n\n";

    std::cout << "StaticVector Benchmark:\n";
    float t = time_it<boost::StaticVector<std::set<std::size_t>,N > >();
    std::cout << "Total time = " << t << "\n\n";

    std::cout << "Vector: Benchmark\n";
    t = time_it<std::vector<std::set<std::size_t> > >();
    std::cout << "Total time = " << t << '\n';
  }catch(std::exception e){
    std::cout << e.what();
  }
}

bool some_test = true;

Вот класс в StaticVector.hpp:

/**
 * @file   StaticVector.hpp
 * @date   Feb 23, 2011
 * @brief  Vector style class with fixed capacity.
 *
 * The following code declares class StaticVector,
 * an STL container (as wrapper) for a statically allocated vector with a constant size limit.
 *  StaticVector is not accepted as part of boost.
 *
 * (C) Carnegie Mellon University 2011
 * @author Andrew Hundt <ahundt@cmu.edu>
 *
 * based on boost::array
 * The original author site of boost::array is at: http://www.josuttis.com/
 * (C) Copyright Nicolai M. Josuttis 2001.
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * 09 Oct 2011 - (ath) eliminated construction of objects on initialization of StaticVector
 * 23 Feb 2011 - (ath) converted to boost::StaticVector
 * 28 Dec 2010 - (mtc) Added cbegin and cend (and crbegin and crend) for C++Ox compatibility.
 * 10 Mar 2010 - (mtc) fill method added, matching resolution of the standard library working group.
 *      See <http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#776> or Trac issue #3168
 *      Eventually, we should remove "assign" which is now a synonym for "fill" (Marshall Clow)
 * 10 Mar 2010 - added workaround for SUNCC and !STLPort [trac #3893] (Marshall Clow)
 * 29 Jan 2004 - c_array() added, BOOST_NO_PRIVATE_IN_AGGREGATE removed (Nico Josuttis)
 * 23 Aug 2002 - fix for Non-MSVC compilers combined with MSVC libraries.
 * 05 Aug 2001 - minor update (Nico Josuttis)
 * 20 Jan 2001 - STLport fix (Beman Dawes)
 * 29 Sep 2000 - Initial Revision (Nico Josuttis)
 *
 * Jan 29, 2004
 */

#ifndef BOOST_STATIC_VECTOR_HPP
#define BOOST_STATIC_VECTOR_HPP

#include <boost/config.hpp>
#include <boost/detail/workaround.hpp>

#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)  
# pragma warning(push)  
# pragma warning(disable:4996) // 'std::equal': Function call with parameters that may be unsafe
# pragma warning(disable:4510) // boost::StaticVector<T,N>' : default constructor could not be generated
# pragma warning(disable:4610) // warning C4610: class 'boost::StaticVector<T,N>' can never be instantiated - user defined constructor required
#endif

#include <cstddef>
#include <stdexcept>
#include <boost/assert.hpp>
#if ((BOOST_VERSION / 100) % 1000) > 44
#include <boost/swap.hpp>
#endif
// Handles broken standard libraries better than <iterator>
#include <boost/detail/iterator.hpp>
#include <boost/throw_exception.hpp>
#include <algorithm>

// FIXES for broken compilers
#include <boost/config.hpp>

// Selection of types for internal storage
#include <boost/integer.hpp>
#include <boost/type_traits/alignment_of.hpp>
#include <boost/type_traits/aligned_storage.hpp>
#include <boost/type_traits/has_trivial_destructor.hpp>


namespace boost {

    template<class T, std::size_t N>
    class StaticVector {
      public:
        // type definitions
        typedef T                                              value_type;
        typedef T*                                             pointer;
        typedef const T*                                       const_pointer;
        typedef T*                                             iterator;
        typedef const T*                                       const_iterator;
        typedef T&                                             reference;
        typedef const T&                                       const_reference;
        typedef typename boost::aligned_storage<               
                           sizeof(T),                          
                           boost::alignment_of<T>::value       
                         >::type                               aligned_storage;
        typedef typename boost::uint_value_t<N>::least         size_type;
        typedef std::size_t                                    max_size_type;
        typedef std::ptrdiff_t                                 difference_type;

      private:
        size_type m_size; // fastest type that can accomodate N
        aligned_storage elems[N];    // fixed-size array of memory aligned elements of type T

      public:

        // iterator support
        iterator        begin()       { return reinterpret_cast<iterator>(elems); }
        const_iterator  begin() const { return reinterpret_cast<const_iterator>(elems); }
        const_iterator cbegin() const { return reinterpret_cast<const_iterator>(elems); }

        iterator        end()       { return to_object(m_size); }
        const_iterator  end() const { return to_object(m_size); }
        const_iterator cend() const { return to_object(m_size); }

        // reverse iterator support
#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_MSVC_STD_ITERATOR) && !defined(BOOST_NO_STD_ITERATOR_TRAITS)
        typedef std::reverse_iterator<iterator> reverse_iterator;
        typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
#elif defined(_MSC_VER) && (_MSC_VER == 1300) && defined(BOOST_DINKUMWARE_STDLIB) && (BOOST_DINKUMWARE_STDLIB == 310)
        // workaround for broken reverse_iterator in VC7
        typedef std::reverse_iterator<std::_Ptrit<value_type, difference_type, iterator,
                                      reference, iterator, reference> > reverse_iterator;
        typedef std::reverse_iterator<std::_Ptrit<value_type, difference_type, const_iterator,
                                      const_reference, iterator, reference> > const_reverse_iterator;
#elif defined(_RWSTD_NO_CLASS_PARTIAL_SPEC) 
        typedef std::reverse_iterator<iterator, std::random_access_iterator_tag, 
              value_type, reference, iterator, difference_type> reverse_iterator; 
        typedef std::reverse_iterator<const_iterator, std::random_access_iterator_tag,
              value_type, const_reference, const_iterator, difference_type> const_reverse_iterator;
#else
        // workaround for broken reverse_iterator implementations
        typedef std::reverse_iterator<iterator,T> reverse_iterator;
        typedef std::reverse_iterator<const_iterator,T> const_reverse_iterator;
#endif

        reverse_iterator rbegin() { return reverse_iterator(end()); }
        const_reverse_iterator rbegin() const {
            return const_reverse_iterator(end());
        }
        const_reverse_iterator crbegin() const {
            return const_reverse_iterator(end());
        }

        reverse_iterator rend() { return reverse_iterator(begin()); }
        const_reverse_iterator rend() const {
            return const_reverse_iterator(begin());
        }
        const_reverse_iterator crend() const {
            return const_reverse_iterator(begin());
        }

        StaticVector():m_size(0){}

        StaticVector(size_type n, const_reference value):
          m_size(n)
        {
          insert(begin(),n,value);
        }

        template<typename InputIterator>
        StaticVector(InputIterator first, InputIterator last):
          m_size(last-first)
        {
          capacitycheck(size());
          std::copy(first,last,begin());
        }

        template<std::size_t SizeRHS>
        StaticVector(const StaticVector<T,SizeRHS>& rhs):
          m_size(rhs.size())
        {
          capacitycheck(rhs.size());
          std::copy(rhs.begin(),rhs.end(),begin());
        }

        ~StaticVector(){
          destroy_array(::boost::has_trivial_destructor<T>());
          m_size=0;
        }

        void push_back (const_reference x){
          capacitycheck(size()+1);

          new (to_object(size())) T(x);
          m_size++;
        }

        void pop_back(){
          if(!empty()){
            to_object(size()-1)->~T();
            m_size--;
          } else {
            boost::throw_exception( std::out_of_range("StaticVector<> pop called on empty container."));
          }
        }

        iterator insert(iterator pos, const_reference x){
          capacitycheck(size()+1);
          std::copy_backward(pos,end(),end()+1);
          *pos = x;
          m_size++;
          return pos;
        }

        void insert(iterator pos, max_size_type n, const_reference x){
          capacitycheck(size()+n);
          std::copy_backward(pos,end(),end()+n);
          std::fill(pos,pos+n,x);
          m_size+=n;
        }

        template <typename InputIterator>
        void insert(iterator pos, InputIterator first, InputIterator last){
          max_size_type n = last - first;
          capacitycheck(size()+n);
          std::copy_backward(pos,end(),end()+n);
          std::copy(first,last,pos);
        }

        iterator erase(iterator pos){
          rangecheck(pos-begin());
          pos->~T();
          std::copy(pos+1,end(),pos);
          m_size--;
          return pos;
        }

        iterator erase(iterator first, iterator last){
          std::ptrdiff_t n = last-first;
            if (n>0) {
            rangecheck(size()-n);
            for(iterator it = first; it!=last; it++){
              it->~T();
            }
            std::copy(last,end(),first);
            m_size -= n;
          }
          return first;
        }

        void clear(){
          erase(begin(),end());
        }

        void resize(max_size_type n, const_reference t = T() ){
          capacitycheck(n);
          if(n - m_size > 0){
            std::fill_n(end(), n-m_size, t);
          } else {
            erase(begin()+n,end());
          }
      m_size = n;
    }

        void reserve(max_size_type n){
          capacitycheck(n);
        }

        // operator[]
        reference operator[](max_size_type i) 
        { 
            BOOST_ASSERT( i < N || i < size() && "StaticVector<>: out of range" );
            return *to_object(i);
        }

        const_reference operator[](max_size_type i) const 
        {     
            BOOST_ASSERT( i < N || i < size() && "StaticVector<>: out of range" );
            return *to_object(i); 
        }

        // at() with range check
        reference at(max_size_type i) { rangecheck(i); return *to_object(i); }
        const_reference at(max_size_type i) const { rangecheck(i); return *to_object(i); }

        // front() and back()
        reference front() 
        { 
            return begin(); 
        }

        const_reference front() const 
        {
            return begin();
        }

        reference back() 
        { 
            return *to_object(size()-1);
        }

        const_reference back() const 
        { 
            return *to_object(size()-1);
        }

        // capacity is constant, size varies
        max_size_type size() const { return m_size; }
        static max_size_type capacity() { return N; }
        bool empty() { return m_size == 0; }
        static max_size_type max_size() { return N; }
        enum { static_size = N };

        // swap (note: linear complexity)
        void swap (StaticVector<T,N>& y) {
#if ((BOOST_VERSION / 100) % 1000) > 44
            for (size_type i = 0; i < N; ++i)
                boost::swap(*to_object(i),*y.to_object(i));
            boost::swap(m_size,y.m_size);
#else
            std::swap_ranges(begin(),end(),y.begin());
            std::swap(m_size,y.m_size);
#endif
        }

        // direct access to data (read-only)
        const_pointer data() const { return elems; }
        pointer data() { return elems; }

        // use array as C array (direct read/write access to data)
        pointer c_array() { return elems; }

        // assignment with type conversion
        template <typename T2>
        StaticVector<T,N>& operator= (const StaticVector<T2,N>& rhs) {
            std::copy(rhs.begin(),rhs.end(), begin());
            m_size = rhs.size();
            return *this;
        }

        // assign one value to all elements
        void assign (const T& value) { fill ( value ); }    // A synonym for fill
        void fill   (const T& value)
        {
            std::fill_n(begin(),size(),value);
        }

        // check range (may be private because it is static)
       void rangecheck (max_size_type i) const {
            if (i >= size()) {
                std::out_of_range e("StaticVector<>: index out of range");
                boost::throw_exception(e);
            }
        }

       // check range (may be private because it is static)
      void capacitycheck (max_size_type i) const {
           if (i > capacity()) {
               std::out_of_range e("StaticVector<>: index out of capacity");
               boost::throw_exception(e);
           }
       }

private:
    inline const_pointer to_object(size_type index) const {
        return reinterpret_cast<const_pointer>(elems+index);
    }

    inline pointer to_object(size_type index) {
        return reinterpret_cast<pointer>(elems+index);
    }

    // T has a trivial destructor, do nothing
    inline void destroy_array(const boost::true_type&) {}

    // T has a destructor, destroy each object 
    inline void destroy_array(const boost::false_type&) {
        for(iterator first = begin(); first != end(); ++first) {
           first->~T();
        }
    }
}; // class StaticVector

#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)
    template< class T >
    class StaticVector< T, 0 > {

      public:
        // type definitions
        typedef T                                              value_type;
        typedef T*                                             iterator;
        typedef const T*                                       const_iterator;
        typedef T&                                             reference;
        typedef const T&                                       const_reference;
        typedef typename boost::aligned_storage<               
                           sizeof(T),                          
                           boost::alignment_of<T>::value       
                         >::type                               aligned_storage;
        typedef typename boost::uint_value_t<0>::least         size_type;
        typedef std::size_t                                    max_size_type;
        typedef std::ptrdiff_t                                 difference_type;

        // iterator support
        iterator        begin()       { return       iterator( reinterpret_cast<       T * >( this ) ); }
        const_iterator  begin() const { return const_iterator( reinterpret_cast< const T * >( this ) ); }
        const_iterator cbegin() const { return const_iterator( reinterpret_cast< const T * >( this ) ); }

        iterator        end()       { return  begin(); }
        const_iterator  end() const { return  begin(); }
        const_iterator cend() const { return cbegin(); }

        // reverse iterator support
#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_MSVC_STD_ITERATOR) && !defined(BOOST_NO_STD_ITERATOR_TRAITS)
        typedef std::reverse_iterator<iterator> reverse_iterator;
        typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
#elif defined(_MSC_VER) && (_MSC_VER == 1300) && defined(BOOST_DINKUMWARE_STDLIB) && (BOOST_DINKUMWARE_STDLIB == 310)
        // workaround for broken reverse_iterator in VC7
        typedef std::reverse_iterator<std::_Ptrit<value_type, difference_type, iterator,
                                      reference, iterator, reference> > reverse_iterator;
        typedef std::reverse_iterator<std::_Ptrit<value_type, difference_type, const_iterator,
                                      const_reference, iterator, reference> > const_reverse_iterator;
#elif defined(_RWSTD_NO_CLASS_PARTIAL_SPEC) 
        typedef std::reverse_iterator<iterator, std::random_access_iterator_tag, 
              value_type, reference, iterator, difference_type> reverse_iterator; 
        typedef std::reverse_iterator<const_iterator, std::random_access_iterator_tag,
              value_type, const_reference, const_iterator, difference_type> const_reverse_iterator;
#else
        // workaround for broken reverse_iterator implementations
        typedef std::reverse_iterator<iterator,T> reverse_iterator;
        typedef std::reverse_iterator<const_iterator,T> const_reverse_iterator;
#endif

        reverse_iterator rbegin() { return reverse_iterator(end()); }
        const_reverse_iterator rbegin() const {
            return const_reverse_iterator(end());
        }
        const_reverse_iterator crbegin() const {
            return const_reverse_iterator(end());
        }

        reverse_iterator rend() { return reverse_iterator(begin()); }
        const_reverse_iterator rend() const {
            return const_reverse_iterator(begin());
        }
        const_reverse_iterator crend() const {
            return const_reverse_iterator(begin());
        }

        void push_back (const_reference x){
          failed_rangecheck();
        }

        void pop_back(){
          failed_rangecheck();
        }

        iterator insert(iterator pos, const_reference x){
          return failed_rangecheck();
        }

        void insert(iterator pos, max_size_type n, const_reference x){
          failed_rangecheck();
        }

        template <typename InputIterator>
        void insert(iterator pos, InputIterator first, InputIterator last){
          failed_rangecheck();
        }

        iterator erase(iterator pos){
          return failed_rangecheck();
        }

        iterator erase(iterator first, iterator last){
          return failed_rangecheck();
        }

        void clear(){
        }

        void resize(max_size_type n, const_reference t = T() ){
          failed_rangecheck();
        }

        void reserve(size_type n){
          failed_rangecheck();
        }

        // operator[]
        reference operator[](max_size_type /*i*/)
        {
            return failed_rangecheck();
        }

        const_reference operator[](max_size_type /*i*/) const
        {
            return failed_rangecheck();
        }

        // at() with range check
        reference at(max_size_type /*i*/)               {   return failed_rangecheck(); }
        const_reference at(max_size_type /*i*/) const   {   return failed_rangecheck(); }

        // front() and back()
        reference front()
        {
            return failed_rangecheck();
        }

        const_reference front() const
        {
            return failed_rangecheck();
        }

        reference back()
        {
            return failed_rangecheck();
        }

        const_reference back() const
        {
            return failed_rangecheck();
        }

        // size is constant
        static max_size_type size() { return 0; }
        static bool empty() { return true; }
        static max_size_type max_size() { return 0; }
        enum { static_size = 0 };

        void swap (StaticVector<T,0>& /*y*/) {
        }

        // direct access to data (read-only)
        const T* data() const { return 0; }
        T* data() { return 0; }

        // use array as C array (direct read/write access to data)
        T* c_array() { return 0; }

        // assignment with type conversion
        template <typename T2>
        StaticVector<T,0>& operator= (const StaticVector<T2,0>& ) {
            return *this;
        }

        // assign one value to all elements
        void assign (const T& value) { fill ( value ); }
        void fill   (const T& ) {}

        // check range (may be private because it is static)
        static reference failed_rangecheck () {
                std::out_of_range e("attempt to access element of an empty StaticVector");
                boost::throw_exception(e);
#if defined(BOOST_NO_EXCEPTIONS) || !defined(BOOST_MSVC)
                //
                // We need to return something here to keep
                // some compilers happy: however we will never
                // actually get here....
                //
                static T placeholder;
                return placeholder;
#endif
            }
    };
#endif

    // comparisons
    template<class T, std::size_t N>
    bool operator== (const StaticVector<T,N>& x, const StaticVector<T,N>& y) {
        return std::equal(x.begin(), x.end(), y.begin());
    }
    template<class T, std::size_t N>
    bool operator< (const StaticVector<T,N>& x, const StaticVector<T,N>& y) {
        return std::lexicographical_compare(x.begin(),x.end(),y.begin(),y.end());
    }
    template<class T, std::size_t N>
    bool operator!= (const StaticVector<T,N>& x, const StaticVector<T,N>& y) {
        return !(x==y);
    }
    template<class T, std::size_t N>
    bool operator> (const StaticVector<T,N>& x, const StaticVector<T,N>& y) {
        return y<x;
    }
    template<class T, std::size_t N>
    bool operator<= (const StaticVector<T,N>& x, const StaticVector<T,N>& y) {
        return !(y<x);
    }
    template<class T, std::size_t N>
    bool operator>= (const StaticVector<T,N>& x, const StaticVector<T,N>& y) {
        return !(x<y);
    }

    // global swap()
    template<class T, std::size_t N>
    inline void swap (StaticVector<T,N>& x, StaticVector<T,N>& y) {
        x.swap(y);
    }

#if defined(__SUNPRO_CC)
//  Trac ticket #4757; the Sun Solaris compiler can't handle
//  syntax like 'T(&get_c_array(boost::StaticVector<T,N>& arg))[N]'
//  
//  We can't just use this for all compilers, because the 
//      borland compilers can't handle this form. 
    namespace detail {
       template <typename T, std::size_t N> struct c_array
       {
           typedef T type[N];
       };
    }

   // Specific for boost::StaticVector: simply returns its elems data member.
   template <typename T, std::size_t N>
   typename detail::c_array<T,N>::type& get_c_array(boost::StaticVector<T,N>& arg)
   {
       return arg.elems;
   }

   // Specific for boost::StaticVector: simply returns its elems data member.
   template <typename T, std::size_t N>
   typename const detail::c_array<T,N>::type& get_c_array(const boost::StaticVector<T,N>& arg)
   {
       return arg.elems;
   }
#else
// Specific for boost::StaticVector: simply returns its elems data member.
    template <typename T, std::size_t N>
    T(&get_c_array(boost::StaticVector<T,N>& arg))[N]
    {
        return arg.elems;
    }

    // Const version.
    template <typename T, std::size_t N>
    const T(&get_c_array(const boost::StaticVector<T,N>& arg))[N]
    {
        return arg.elems;
    }
#endif

#if 0
    // Overload for std::array, assuming that std::array will have
    // explicit conversion functions as discussed at the WG21 meeting
    // in Summit, March 2009.
    template <typename T, std::size_t N>
    T(&get_c_array(std::array<T,N>& arg))[N]
    {
        return static_cast<T(&)[N]>(arg);
    }

    // Const version.
    template <typename T, std::size_t N>
    const T(&get_c_array(const std::array<T,N>& arg))[N]
    {
        return static_cast<T(&)[N]>(arg);
    }
#endif


} /* namespace boost */


#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)  
# pragma warning(pop)  
#endif 

#endif /*BOOST_STATIC_VECTOR_HPP*/

Ответы [ 2 ]

4 голосов
/ 13 октября 2011

Вы нарушили правило трех . Вам нужен конструктор копирования. Обратите внимание, что ваш конструктор, принимающий const StaticVector<T,SizeRHS>&, не является конструктором копирования, поскольку это шаблон. Следовательно, когда StaticVector копируется (например, возможно, когда возвращается из функции), используется сгенерированный компилятором конструктор копирования, копирующий хранилище (это нормально, это POD). Во время уничтожения деструктор обращается к хранилищу, как будто оно содержит живые объекты, и это ужасно.

1 голос
/ 13 октября 2011

boost::aligned_storage на самом деле является типом POD (требуется C ++ 11) и не знает, какой тип хранится в нем. Это просто обеспечивает «хранение». Если вы хотите скопировать объекты из одного хранилища в другое, вам придется выполнить копирование самостоятельно.

Рассматривали ли вы возможность внедрения собственного распределителя на основе стекового хранилища и повторного использования стандартных контейнеров?

...