Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/126.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在C+;中创建一个不变且高效的类的惯用方法+;_C++_Constants_Immutability_Const Cast - Fatal编程技术网

C++ 在C+;中创建一个不变且高效的类的惯用方法+;

C++ 在C+;中创建一个不变且高效的类的惯用方法+;,c++,constants,immutability,const-cast,C++,Constants,Immutability,Const Cast,我想做这样的事情(C#) 问题1: OtherImmutableObject o1(1,2); OtherImmutableObject o2(2,3); o1 = o2; // error: use of deleted function 'OtherImmutableObject& OtherImmutableObject::operator=(const OtherImmutableObject&)` 编辑:这很重要,因为我想将不可变对象存储在std::vector中,但

我想做这样的事情(C#)

问题1:

OtherImmutableObject o1(1,2);
OtherImmutableObject o2(2,3);
o1 = o2; // error: use of deleted function 'OtherImmutableObject& OtherImmutableObject::operator=(const OtherImmutableObject&)`
编辑:这很重要,因为我想将不可变对象存储在
std::vector
中,但收到
错误:使用删除的函数“OtherImmutableObject&OtherImmutableObject::operator=(OtherImmutableObject&&)

2。使用get方法和返回值,但这意味着必须复制大型对象,我想知道如何避免这种低效。建议使用get解决方案,但它没有说明如何在不复制原始对象的情况下处理传递的非基本对象

解决方案2:

class OtherImmutableObject {
    int i1;
    int i2;
public:
    OtherImmutableObject(int i1, int i2) : i1(i1), i2(i2) {}
    int GetI1() { return i1; }
    int GetI2() { return i2; }
}

class ImmutableObject {
    int i1;
    OtherImmutableObject o;
    std::vector<OtherImmutableObject> v;
public:
    ImmutableObject(int i1, OtherImmutableObject o,
        std::vector<OtherImmutableObject> v) : i1(i1), o(o), v(v) {}
    int GetI1() { return i1; }
    OtherImmutableObject GetO() { return o; } // Copies a value that should be immutable and therefore able to be safely used elsewhere.
    std::vector<OtherImmutableObject> GetV() { return v; } // Copies the vector.
}
问题3:

ImmutableObject immutable_object(1,o,v);
// elsewhere in code...
OtherImmutableObject& other_immutable_object = immutable_object.GetO();
// Somewhere else immutable_object goes out of scope, but not other_immutable_object
// ...and then...
other_immutable_object.GetI1();
// The previous line is undefined behaviour as immutable_object.o will have been deleted with immutable_object going out of scope

由于从任何
Get
方法返回引用,可能会发生未定义的行为。

通过利用
std::unique_ptr
std::shared_ptr
基本上可以获得所需。如果您只需要这些对象中的一个,但允许将其四处移动,则可以使用
std::unique\u ptr
。如果要允许多个对象(“副本”)都具有相同的值,则可以使用
std::shared\u Ptr
。使用别名来缩短名称并提供一个工厂函数,它变得非常轻松。这将使您的代码看起来像:

class ImmutableClassImpl {
public: 
    const int i;
    const OtherImmutableClass o;
    const ReadOnlyCollection<OtherImmutableClass> r;

    public ImmutableClassImpl(int i, OtherImmutableClass o, 
        ReadOnlyCollection<OtherImmutableClass> r) : i(i), o(o), r(r) {}
}

using Immutable = std::unique_ptr<ImmutableClassImpl>;

template<typename... Args>
Immutable make_immutable(Args&&... args)
{
    return std::make_unique<ImmutableClassImpl>(std::forward<Args>(args)...);
}

int main()
{
    auto first = make_immutable(...);
    // first points to a unique object now
    // can be accessed like
    std::cout << first->i;
    auto second = make_immutable(...);
    // now we have another object that is separate from first
    // we can't do
    // second = first;
    // but we can transfer like
    second = std::move(first);
    // which leaves first in an empty state where you can give it a new object to point to
}
然后两个对象都指向同一个对象,但都不能修改它

  • 您确实需要某种类型加值语义的不可变对象(因为您关心运行时性能并希望避免堆)。只需使用所有数据成员定义一个
    struct

    struct Immutable {
        const std::string str;
        const int i;
    };
    
    您可以实例化和复制它们,读取数据成员,但仅此而已。移动从另一个实例的右值引用构造实例时仍然复制

    Immutable obj1{"...", 42};
    Immutable obj2 = obj1;
    Immutable obj3 = std::move(obj1); // Copies, too
    
    obj3 = obj2; // Error, cannot assign
    
    这样,您就可以真正确保类的每个用法都尊重不变性(假设没有人做不好的
    const\u cast
    事情)。附加功能可以通过自由函数提供,将成员函数添加到数据成员的只读聚合中没有意义

  • 您需要1.,仍然使用值语义,但稍微放松(这样对象就不再是真正不变的),并且您还担心为了运行时性能需要移动构造。无法绕过
    private
    数据成员和getter成员函数:

    class Immutable {
       public:
          Immutable(std::string str, int i) : str{std::move(str)}, i{i} {}
    
          const std::string& getStr() const { return str; }
          int getI() const { return i; }
    
       private:
          std::string str;
          int i;
    };
    
    用法是一样的,但move构造确实会移动

    Immutable obj1{"...", 42};
    Immutable obj2 = obj1;
    Immutable obj3 = std::move(obj1); // Ok, does move-construct members
    
    现在,您可以控制是否允许分配任务。如果不需要,只需删除赋值运算符,否则使用编译器生成的赋值运算符或实现自己的赋值运算符

    obj3 = obj2; // Ok if not manually disabled
    
  • 您不关心值语义和/或原子引用计数增量在您的场景中是可以的。使用中描述的解决方案


  • 我认为最惯用的方式是:

    struct OtherImmutable {
        int i1;
        int i2;
    
        OtherImmutable(int i1, int i2) : i1(i1), i2(i2) {}
    };
    
    但是。。。那不是一成不变的吗

    确实如此,但您可以将其作为值传递:

    void frob1() {
        OtherImmutable oi;
        oi = frob2(oi);
    }
    
    auto frob2(OtherImmutable oi) -> OtherImmutable {
        // cannot affect frob1 oi, since it's a copy
    }
    
    更好的是,不需要局部变异的地方可以将其局部变量定义为常量:

    auto frob2(OtherImmutable const oi) -> OtherImmutable {
        return OtherImmutable{oi.i1 + 1, oi.i2};
    }
    

    由于C++的普适语义,C++中的不可改变性不能直接与大多数其他流行语言的不可改变性相比较。你必须弄清楚“不可变”是什么意思

    您希望能够将新值分配给
    OtherImmutableObject
    类型的变量。这是有道理的,因为您可以使用C#中类型为
    ImmutableObject
    的变量来实现这一点

    在这种情况下,获取所需语义的最简单方法是

    struct OtherImmutableObject {
        int i1;
        int i2;
    };
    
    这看起来可能是可变的。毕竟,你可以写作

    OtherImmutableObject x{1, 2};
    x.i1 = 3;
    
    但是第二行的效果(忽略并发性…)与

    x = OtherImmutableObject{3, x.i2};
    
    因此,如果您想允许赋值给
    OtherImmutableObject
    类型的变量,那么禁止直接赋值给成员是没有意义的,因为它不提供任何额外的语义保证;它所做的只是使同一抽象操作的代码变慢。(在这种情况下,大多数优化编译器可能会为这两个表达式生成相同的代码,但如果其中一个成员是
    std::string
    ,它们可能不够聪明,无法做到这一点。)

    注意这是C++中基本上所有标准类型的行为,包括<代码> int <代码>,<代码> STD::复杂< /COD>,<代码> STD::String 等等。它们都是可变的,在这个意义上,你可以给它们分配新的值,并且在你可以做的唯一的抽象意义上是不可变的(抽象的)。改变它们就是给它们分配新的值,就像C#中的不可变引用类型一样

    如果您不想要这种语义,那么您唯一的其他选择就是禁止赋值。我建议通过将变量声明为
    const
    ,而不是将该类型的所有成员声明为
    const
    ,因为它为您提供了更多使用该类的选项。例如,您可以创建该类的一个初始可变实例,在其中构建一个值,然后仅使用
    const
    引用“冻结”该类,就像将
    StringBuilder
    转换为
    string
    ,但无需复制它的开销

    (将所有成员声明为
    const
    的一个可能原因可能是,它在某些情况下允许更好的优化。例如,如果函数获得
    OtherImmutableObject const&
    ,并且编译器看不到调用站点,则在调用其他未知代码时缓存成员的值是不安全的,因为ing对象可能没有
    const
    限定符。但是如果实际成员声明为
    const
    ,那么我认为缓存这些值是安全的。)

    C++不太能够将类预定义为不可变或常量

    在某个时刻,您可能会得出结论,您不应该使用
    constauto frob2(OtherImmutable const oi) -> OtherImmutable {
        return OtherImmutable{oi.i1 + 1, oi.i2};
    }
    
    struct OtherImmutableObject {
        int i1;
        int i2;
    };
    
    OtherImmutableObject x{1, 2};
    x.i1 = 3;
    
    x = OtherImmutableObject{3, x.i2};
    
    typedef class _some_SUPER_obtuse_CLASS_NAME_PLEASE_DONT_USE_THIS { } const Immutable;
    
    /* const-correct */ class C {
       int f1_;
       int f2_;
    
       const int f3_; // Semantic constness : initialized and never changed.
    };
    
    shared_ptr<const C> ptr = make_shared<const C>(f1, f2, f3);
    
    struct immu_tag_t {};
    template<class T>
    struct immu:std::shared_ptr<T const>
    {
      using base = std::shared_ptr<T const>;
    
      immu():base( std::make_shared<T const>() ) {}
    
      template<class A0, class...Args,
        std::enable_if_t< !std::is_base_of< immu_tag_t, std::decay_t<A0> >{}, bool > = true,
        std::enable_if_t< std::is_construtible< T const, A0&&, Args&&... >{}, bool > = true
      >
      immu(A0&& a0, Args&&...args):
        base(
          std::make_shared<T const>(
            std::forward<A0>(a0), std::forward<Args>(args)...
          )
        )
      {}
      template<class A0, class...Args,
        std::enable_if_t< std::is_construtible< T const, std::initializer_list<A0>, Args&&... >{}, bool > = true
      >
      immu(std::initializer_list<A0> a0, Args&&...args):
        base(
          std::make_shared<T const>(
            a0, std::forward<Args>(args)...
          )
        )
      {}
    
      immu( immu_tag_t, std::shared_ptr<T const> ptr ):base(std::move(ptr)) {}
      immu(immu&&)=default;
      immu(immu const&)=default;
      immu& operator=(immu&&)=default;
      immu& operator=(immu const&)=default;
    
      template<class F>
      immu modify( F&& f ) const {
        std::shared_ptr<T> ptr;
        if (!*this) {
          ptr = std::make_shared<T>();
        } else {
          ptr = std::make_shared<T>(**this);
        }
        std::forward<F>(f)(*ptr);
        return {immu_tag_t{}, std::move(ptr)};
      }
    };
    
    immu<int> immu_null_int{ immu_tag_t{}, {} };
    
    immu<int> immu_int;
    
    immu<int> immu_int = 7;
    
    struct data;
    using immu_data = immu<data>;
    struct data {
      int i;
      other_immutable_class o;
      std::vector<other_immutable_class> r;
      data( int i_in, other_immutable_class o_in, std::vector<other_immutable_class> r_in ):
        i(i_in), o(std::move(o_in)), r( std::move(r_in))
      {}
    };
    
    immu_data a( 7, other_immutable_class{}, {} );
    immu_data b = a.modify([&](auto& b){ ++b.i; b.r.emplace_back() });
    
    class ImmutableObject {
        ImmutableObject(int i1, int i2) : i1(i1), i2(i2) {}
        const int i1;
        const int i2;
    }
    ImmutableObject o1(1,2):
    ImmutableObject o2(2,3);
    o1 = o2; // Doesn't compile, because immutable objects are by definition not mutable.
    
    class ImmutableObject {
        ImmutableObject(int i1, int i2) : i1(i1), i2(i2) {}
        const int i1;
        const int i2;
    }
    std::shared_ptr<ImmutableObject> o1 = std::make_shared<ImmutableObject>(1,2);
    std::shared_ptr<ImmutableObject> o2 = std::make_shared<ImmutableObject>(2,3);
    o1 = o2; // Does compile because shared_ptr is mutable.