【问题标题】:Why is there an error message when splicing list elements from lists with different allocators? And how can this be fixed?为什么从具有不同分配器的列表中拼接列表元素时会出现错误消息?如何解决这个问题?
【发布时间】:2018-02-13 13:24:42
【问题描述】:

您好,我正在尝试将元素从 (end()-1) 的一个列表(在示例中为 foo)转移/移动到另一个列表(在示例中称为 bar)到 begin() 位置。

唯一的问题是其中一个列表使用了定制的分配器。 这可能会导致以下错误消息:

../src/TestAllocator.cpp:120:28: error: 
no matching function for call to
‘std::list<int>::splice  (  std::_List_iterator<int>&, 
                            std::list<int, CustomAllocator<int> >&, 
                            std::_List_iterator<int>&)’

我的问题是:

  • 为什么从列表中拼接列表元素时出现错误提示 使用不同的分配器?

  • 如何解决这个问题?

Code on Coliru

#include <limits>   // numeric_limits
#include <iostream>
#include <typeinfo> // typeid

// container
#include <vector>
#include <list>
#include <forward_list>

/// state stored as static member(s) of an auxiliary(=hilfs/zusatz) class
struct CustomAllocatorState {
    static std::size_t m_totalMemAllocated;
};
std::size_t CustomAllocatorState::m_totalMemAllocated = 0;

/// @brief  The @a custom allocator
/// @tparam  T  Type of allocated object
template<typename T>
class CustomAllocator {
public:
    // type definitions
    typedef std::size_t     size_type;          /** Quantities of elements */
    typedef std::ptrdiff_t  difference_type;    /** Difference between two pointers */
    typedef T*              pointer;            /** Pointer to element */
    typedef const T*        const_pointer;      /** Pointer to constant element */
    typedef T&              reference;          /** Reference to element */
    typedef const T&        const_reference;    /** Reference to constant element */
    typedef T               value_type;         /** Element type */

    template<typename U>
    struct rebind {
        typedef CustomAllocator<U> other;
    };


    CustomAllocator() throw() {
        std::cout << "construct " << typeid(T).name() << std::endl;
    }

    CustomAllocator(const CustomAllocator&) throw() {
        std::cout << "copy construct " << typeid(T).name() << std::endl;
    }

    template<class U>
    CustomAllocator() throw() {
    }

    ~CustomAllocator() throw() {}


    // allocate but don't initialize num elements of type T
    pointer allocate(size_type num, const void* = 0) {
        CustomAllocatorState::m_totalMemAllocated += num * sizeof(T);
        // print message and allocate memory with global new
        std::cout << "allocate " << num << " element(s)" << " of size "
                << sizeof(T) << std::endl;
        pointer ret = (pointer) (::operator new(num * sizeof(T)));
        std::cout << " allocated at: " << (void*) ret << std::endl;
        return ret;
    }

    // deallocate storage p of deleted elements
    void deallocate(pointer p, size_type num) {
        CustomAllocatorState::m_totalMemAllocated -= num * sizeof(T);
        // print message and deallocate memory with global delete
        std::cout << "deallocate " << num << " element(s)" << " of size "
                << sizeof(T) << " at: " << (void*) p << std::endl;
        ::operator delete((void*) p);
    }

    // initialize elements of allocated storage p with value value
    // no need to call rebind with this variadic template anymore in C++11
    template<typename _U, typename ... _Args>
    void construct(_U* p, _Args&&... args) {
        ::new ((void *) p) _U(std::forward<_Args>(args)...);
    }

    // destroy elements of initialized storage p
    template<typename _U>
    void destroy(_U* p) {
        p->~_U();
    }

    // return address of values
    pointer address (reference value) const {
        return &value;
    }
    const_pointer address (const_reference value) const {
        return &value;
    }


    // return maximum number of elements that can be allocated
    size_type max_size () const throw() {
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }
};

template<typename T, typename U>
inline bool operator==(const CustomAllocator<T>&, const CustomAllocator<U>&) {
    return true;
}

template<typename T, typename U>
inline bool operator!=(const CustomAllocator<T>&, const CustomAllocator<U>&) {
    return false;
}

int main() {
    std::list<int, CustomAllocator<int>> foo;
    std::list<int> bar; // aka  std::list<int, std::allocator<int> > bar;
    foo.push_back(23);
    foo.push_back(12);
    foo.push_back(8);

    // transfer/move element in foo at end() to list bar at position begin()
    auto pos = bar.begin();
    auto other = foo;
    auto it = --(foo.end());
    bar.splice(pos, foo, it);   // here the error: no matching function for call

    std::cout << "---" << std::endl;

    // debug output
    std::cout << "Foo: ";
    for (auto x : foo)
        std::cout << x << " ";
    std::cout << std::endl;

    std::cout << "Bar: ";
    for (auto x : bar)
        std::cout << x << " ";
    std::cout << std::endl;

    std::cout << "alloc_count: " << CustomAllocatorState::m_totalMemAllocated << std::endl;
    std::cout << "---" << std::endl;
    std::cout << '\n';
    return 0;
}

【问题讨论】:

  • 最后一个元素在end()不可用
  • 是的 ^^ 我会改正的 --(foo.end()) 当然
  • 不确定编译器错误,但如果两个容器中的分配器比较不相等,则为未定义行为。
  • 阅读this。它会告诉你将代码放入问题中。
  • @CinCout 如果编译器可以确定 UB 在每个代码路径上,则发出错误而不是程序是在其权限范围内

标签: c++ c++11 splice allocator


【解决方案1】:

列表通过给定的分配器分配内部节点。拥有两个具有不同分配器的列表并尝试将元素从一个移动到另一个意味着:

  • 由Allocator1在List1中分配一个内部节点
  • 将节点移动到List2
  • 稍后通过Allocator2销毁List2中的节点

分配器通常会做一些技巧,例如分配比调试模式下额外检查所需的更多内存,例如用于防止缓冲区欠载/溢出。只有分配器知道它分配了多少内存。所以 Allocator2 不适合释放它没有分配的东西。

在这种特殊情况下,分配器是一个模板参数,它是类型的一部分,使std::list&lt;T, Allocator1&gt; 与std::list&lt;T, Allocator2&gt; 不兼容。

有人可能会争辩说您的CustomAllocator 使用全局operator new 分配内存,因此应该与默认分配器“兼容”。从 C++ 的角度来看,我不知道有什么方法可以使它们兼容,而且我认为这不是一个好主意,因为它开启了以后另一个人可以决定增强你的自定义分配器的可能性意外与默认的不兼容。编译器在这里帮不了你,后果可能是毁灭性的。

【讨论】:

  • 编译器错误是否合理? AFAIK 它是 UB,但这是运行时行为。
  • @CinCout:是的,编译器说它有一个不兼容类型的迭代器
  • “如果:get_allocator() != other.get_allocator(),则行为未定义。” 没有说明编译器错误。 en.cppreference.com/w/cpp/container/list/splice
  • @CinCout: 详细地说,std::list&lt;SomeType, Allocator1&gt; 和 std::list&lt;SomeType, Allocator2&gt; 是完全不同的类型,它们可能会意外地具有相似的行为(和接口!)。例如。 std::list 可以有完全不同的模板专门实现和完全不同的接口,当然这会导致它与标准库不兼容。
  • 如果您存储在列表中的类型未在其他任何地方使用,您可以覆盖该类型的运算符 new 和 delete(而不是自定义分配器)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
相关资源
最近更新 更多