2017-02-25 50 views
0

我正在设计具有共享所有权语义的容器类型。它支持切片,并因此切片共享所有权。我的一个问题是数据共享似乎会干扰常量的正确性,所以我试图关注这一点,但是我对结果并不满意。具有共享所有者语义的容器

下面是一个大大细分版本的我的实际代码:

#include <memory> 
#include <vector> 
#include <algorithm> 

template <typename T> 
class SharedMem 
{ 
public: 
    SharedMem(std::initializer_list<T> init) 
    : m_mem(std::make_shared<std::vector<T>>(init.begin(), init.end())) 
    , m_data(m_mem->data()) 
    , m_size(m_mem->size()) 
    { } 

    SharedMem(SharedMem& other) = default;  // best-effort for copy-construction 
    SharedMem(SharedMem const& other) = delete; // disallow, would circumvent const-correctness 

    SharedMem& operator = (SharedMem const& other) { 
     std::copy(other.m_data, other.m_data + other.m_size, m_data); 
     return *this; 
    } 

    std::size_t size() const 
    { return m_size; } 
    T& operator [] (std::size_t index) 
    { return m_data[index]; } 
    T const& operator [] (std::size_t index) const 
    { return m_data[index]; } 

    SharedMem slice(std::size_t first, std::size_t last) { 
     SharedMem<T> ret(*this); 
     ret.m_data += first; 
     ret.m_size = last - first; 
     return ret; 
    } 
    SharedMem const slice(std::size_t first, std::size_t last) const { 
     SharedMem<T> ret(*this); 
     ret.m_data += first; 
     ret.m_size = last - first; 
     return ret; 
    } 

private: 
    std::shared_ptr<std::vector<T>> m_mem; // shared underlying memory 
    T* m_data;        // start of slice 
    std::size_t m_size;      // size of slice 
}; 

用途:

int main(int argc, char** argv) { 
    SharedMem<int> a { 0, 1, 2, 3, 4 }; 
    SharedMem<int> b { 8, 9 }; 
    SharedMem<int> c = a; // shallow copy of a, data is shared 
    a.slice(1, 3) = b;  // a = [0, 8, 9, 3, 4] 
    c[4] = 6;    // a = [0, 8, 9, 3, 6] 
} 

有个声音告诉我,我在错误的轨道上。我看到下面的问题,我的方法:

  • 它违反了3规则我不喜欢特别需要禁用默认的拷贝构造函数用于固定常量,正确性的缘故。否则,可以创建一个const对象的非const拷贝,后者可以修改前者的元素。
  • 复制构建和分配实施非常不同的操作。这就是我让c = aa.slice(1, 3) = b做正确的事情(实际上非​​常不同的事情)。

我不确定我是否遇到麻烦。问题:

  • 这个设计好吗,还是会引发问题?如果是这样,哪个?
  • 如果存在严重缺陷,如何解决/避免它?

感谢您的任何提示。

+0

赋值似乎中断,因为它可以写出包含范围的界限。 –

+0

@Kerrek SB:当然。正如我写的,代码尽可能简化。实际的代码长度大于1000行,其中更复杂的切片,相应的迭代器和其他功能。它也检查界限:) – tglas

回答

2

您需要分离类型以正确地使用const的方式进行这项工作。对于您发现的非常类似的原因,iteratorconst_iterator对于所有标准库容器都是不同的类型。这说,我认为它高度依赖于你的用例/代码库和编码风格,我是否建议沿着这条路线走下去(因为保护编码器免受可能永远不会成为问题的许多开销在你的用例中)。

如果你想给它一个尝试,一个解决方案可能是这个样子:

namespace detail 
{ 
    template<class T, bool Const> 
    struct SharedInternalsT; 

    template<class T> 
    struct SharedInternalsT<T, true> 
    { 
     const T * m_data; 
     std::size_t m_size; 
    }; 

    template<class T> 
    struct SharedInternalsT<T, false> 
    { 
     T * m_data; 
     std::size_t m_size; 
    }; 

    template<class T> 
    using SharedInternals = SharedInternals<T, false>; 

    template<class T> 
    using ConstSharedInternals = SharedInternals<T, true>; 
} 

template<class T, bool Const> 
class SharedMemT 
{ 
public: 

    using Traits = SharedMemTraits<T, Const>; 
    using Ptr = typename Traits::Ptr; 

    //now we can safely copy in a const correct way. 
    SharedMemT(const SharedMemT & _other) : 
    m_mem(_other.m_mem), 
    m_internals(_other.m_internals) 
    { 

    } 

private: 

    std::shared_ptr<std::vector<T>> m_mem; 
    detail::SharedInternals<T, Const> m_internals; 
}; 

template<class T> 
using SharedMem = SharedMemT<T, false>; 

template<class T> 
using ConstSharedMem = SharedMemT<T, true>; 

这将是迈向解决方案的第一步。为了能够从非const版本(可能通过使用std::enable_if等启用/禁用某些模板化的拷贝构造函数)正确地构造ConstVersions,您很可能必须引入更多的间接方法。正如我所说的,如果您正在构建某种符合标准库的代码,我只会沿着这条路线走下去。如果你只是为你的游戏建立一个小工具或者这些工具,那么忽略const的正确性并且不要浪费你的时间。

+0

感谢您的建议!对常量迭代器的类比是非常好的一点,而且确实很有洞察力。与单个(可能是const限定的)迭代器类型相比,非常需要两种迭代器类型,这对我来说始终是次优设计。但我现在更清楚地看到这个问题。 – tglas