代码之家  ›  专栏  ›  技术社区  ›  Zhro

定义一个接受常量和非常量对象而又不违反常量正确性的类的正确模式是什么?

  •  0
  • Zhro  · 技术社区  · 4 年前

    这个类有许多明显的问题,它旨在说明问题,而不是演示工作代码。

    #include <list>
    
    template<typename T>
    class OnlyLists {
    private:
       const std::list<T>* p_list;
       
    public:
       OnlyLists(std::list<T>* list) : p_list(list) { }
       OnlyLists(const std::list<T>* list) : p_list(list) { }
       
       void modifiesList() {
          const_cast<std::list<T>*>(p_list)->clear();
       }
       
       size_t viewsList() const {
          return p_list->size();
       }
    };
    
    int main() {
       const std::list<int> const_list;
       
       // Constness is preserved
       const OnlyLists<int> a(&const_list);
       
       // Constness is discarded
       OnlyLists<int> b(&const_list);
       
       // Constness is discarded (another inherited problem)
       b = a;
       
       return 0;
    }
    

    我想创建一个可以查看和修改特定对象的类(本例中的列表)。其方法是公开的,因此只有非- const 可以修改列表。问题是,方法上的这种常量只与 OnlyLists 对象为 常量 . 如果我接受 const list<T>* 同时不声明 OnlyList列表 对象为 常量 然后我可以在不应该修改的时候修改列表。

    可以通过使构造函数私有并使对象创建成为一种静态方法来解决此问题,以确保constness,但这种方法完全抛弃了公共构造函数,而是一种反模式。此外,只保留一个指针 p_list 调用任何修改列表的方法时,都已经要求丢弃常量。

    在C++中,我希望一个类能够像这样查看和修改另一个对象的正确模式是什么?

    我能看到的唯一解决方案是,任何同时接受这两者的类 常量 非常量类型只能提供常量视图。但这里唯一的问题是对象构造;实际接口已经保护对象不受修改。

    我想把整个 list 在模板中键入,但如果类已经只接受列表,则这似乎是多余的。如果我将整个容器移动到模板中,则需要添加约束以确保它是一个列表等。当构造函数已经限制自己只接受列表时,这似乎过于复杂。

    0 回复  |  直到 4 年前
        1
  •  0
  •   fabian    4 年前

    C++17解决方案:

    如果您愿意稍微修改一下语法,可以添加 bool modifiable 模板参数到 OnlyLists ,为创建部分规范 modifiable = false 和introcude类模板参数推导( CTAD )推断 modifiable 给定构造函数参数:

    template<typename T, bool modifiable>
    class OnlyLists
    {
    public:
        using ListType = std::list<T>;
    
        OnlyLists(ListType* list) : p_list(list) { }
    
        void modifiesList() {
            p_list->clear();
        }
    
        size_t viewsList() const {
            return p_list->size();
        }
    
    private:
        ListType* p_list;
    };
    
    template<typename T>
    class OnlyLists<T, false>
    {
    public:
        using ListType = std::list<T> const;
    
        OnlyLists(ListType* list) : p_list(list) { }
    
        size_t viewsList() const {
            return p_list->size();
        }
    
    private:
        ListType* p_list;
    };
    
    // modifiable should be true if and only if the type the pointer points to is non-const
    template<typename ListType>
    OnlyLists(ListType*) -> OnlyLists<typename ListType::value_type, !std::is_const_v<ListType>>;
    
    int main()
    {
        std::list<int> non_const_list;
        const std::list<int> const_list;
    
        auto a = OnlyLists(&const_list); // a has type OnlyLists<int, false>
    
        a.viewsList();
        a.modifiesList(); // compiler error
    
        // Constness is discarded
        auto b = OnlyLists(&non_const_list); // b has type OnlyLists<int, true>
    
        b.viewsList();
        b.modifiesList();
    
        a = b; // compiler error
    
        decltype(b) c(nullptr);
        c = b; // Ok. Uses auto-generated a copy assignment operator
    
        return 0;
    }
    
        2
  •  0
  •   Brian Bi    4 年前

    自从 OnlyLists 具有引用语义,其方式与 std::unique_ptr ,通常的做法是使用模板参数的常数来控制突变:

    • OnlyLists<T> 可以存储指向的指针 std::list<T> 和
    • OnlyLists<const T> 可以接受指向以下任一项的指针 标准::列表(&L);T> 或 const std::list<T> ,但将始终存储指向 常量标准::列表(&L);T> ,且仅允许 const 列表上的操作。

    这个 modifiesList() 可能未为定义方法 仅列表(&L);常数T> ,或者它可能已定义,但如果实例化(使用),则会导致编译错误。

    例子:

    template<typename T>
    class OnlyLists {
    private:
       std::list<T>* p_list;
       
    public:
       OnlyLists(std::list<T>* list) : p_list(list) { }
       
       void modifiesList() {
          p_list->clear();
       }
       
       size_t viewsList() const {
          return p_list->size();
       }
    };
    
    // This is a partial specialization for the case where T is const
    template<typename T>
    class OnlyLists<const T> {
    private:
       std::list<T> const* p_list;
       
    public:
       OnlyLists(std::list<T> const* list) : p_list(list) { }
       
       size_t viewsList() const {
          return p_list->size();
       }
    };
    

    如何避免重复的问题 viewsList 留给读者作为练习。

    推荐文章