草庐IT

c++ - 如果将变量设置为新对象,旧对象会发生什么情况?

coder 2024-02-16 原文

假设我们有一个类 X,它没有有一个重载的 operator=() 函数。

class X {
    int n;

    X() {n = 0;}
    X(int _n) {n = _n;}
};

int main() {
    X a;    // (1) an object gets constructed here

    // more code...

    a = X(7);    // (2) another object gets constructed here (?)

    // some more code...

    a = X(12);    // (3) yet another object constructed here (?)
    return 0;
}

是否在(2)处构造了一个新对象?如果是,在 (1) 处构造的旧对象会发生什么情况?它是自动销毁还是释放(是哪个)?是否被覆盖?

在代码 (3) 的下方发生了什么?

最重要的是,是否有可能通过编写上述代码导致内存泄漏?

最佳答案

您需要了解的是,作为新手,您不知道编译器生成的许多“隐式”代码。我们将使用您的 class X 代码作为直接示例:

class X {
    int n;
public: //You didn't include this, but this won't work at all unless your constructors are public
    X() {n = 0;}
    X(int _n) {n = _n;}
};

在代码变成目标代码之前,但在你的编译器得到你的类定义之后,它会将你的类转换成看起来(大致)像这样的东西:

class X {
    int n;
public:
    X() {n = 0;} //Default-Constructor
    X(int _n) {n = _n;} //Other Constructor
    //GENERATED BY COMPILER
    X(X const& x) {n = x.n;} //Copy-Constructor
    X(X && x) {n = x.n;} //Move-Constructor
    X & operator=(X const& x) {n = x.n; return *this;} //Copy-Assignment
    X & operator=(X && x) {n = x.n; return *this;} //Move-Assignment
    ~X() noexcept {} //Destructor
};

关于何时自动创建这些成员的规则并不是很明显 (A good starting reference here),但现在,您可以相信,在这种情况下,这正是发生的事情。

因此,在您的 main 函数中,让我们回顾一下发生了什么,并通过注释引起注意的细节:

int main() {
    X a; //Default-Constructor called
    a = X(7);//Other Constructor called, then Move-Assignment operator called,
    //then Destructor called on temporary created by `X(7)`
    a = X(12); //Same as previous line

    return 0;
    //Destructor called on `a`
}

我们将添加更多行来显示这些调用的大部分(如果不是全部)各种排列:

int main() {
    X a; //Default-Constructor
    X b = a; //Copy-Constructor (uses copy-elision to avoid calling Default + copy-assign)
    X c(5); //Other Constructor
    X d{7}; //Also Other Constructor
    X e(); //Declares a function! Probably not what you intended!
    X f{}; //Default-Constructor
    X g = X(8); //Other Constructor (uses copy-elision to avoid calling Other + move-assign + Destructor)
    X h = std::move(b); //Move-Constructor (uses copy-elision to avoid calling Default + move-assign)
    b = c; //Copy-assignment
    b = std::move(d); //Move-assignment
    d = X{15}; //Other Constructor, then Move-Assignment, then Destructor on `X{15}`.
    //e = f; //Will not compile because `e` is a function declaration!
    return 0;
    //Destructor on `h`
    //Destructor on `g`
    //Destructor on `f`
    //Destructor will NOT be called on `e` because `e` was a function declaration, 
    //not an object, and thus has nothing to clean up!
    //Destructor on `d`
    //Destructor on `c`
    //Destructor on `b`
    //Destructor on `a`
}

这应该涵盖基础知识。

And most importantly, is there ever a chance of causing a memory leak by writing code like in the above?

正如所写,没有。但是,假设您的类(class)做了这样的事情:

class X {
    int * ptr;
public:
    X() {
        ptr = new int{0};
    }
};

现在,您的代码会泄漏,因为每次创建 X 时,您都会有一个永远不会被删除的指针。

要解决这个问题,您需要确保 A) 析构函数正确清理指针,以及 B) 您的复制/移动构造函数/运算符是正确的。

class X {
    int * ptr;
public:
    X() {
        ptr = new int{0};
    }
    X(int val) {
        ptr = new int{val};
    }
    X(X const& x) : X() {
        *ptr = *(x.ptr);
    }
    X(X && x) : X() {
        std::swap(ptr, x.ptr);
    }
    X & operator=(X const& x) {
        *ptr = *(x.ptr);
        return *this;
    }
    X & operator=(X && x) {
        std::swap(ptr, x.ptr);
        return *this;
    }
    ~X() noexcept {
        delete ptr;
    }
};

如果按原样在您的main 函数或我的函数中使用,此代码将不会泄漏内存。但是,当然,如果您这样做并不能阻止泄漏:

int main() {
    X * ptr = new X{};
    return 0;
    //Whelp.
}

一般来说,如果您根本不需要使用指针,建议您改用 std::unique_ptr 之类的东西,因为它免费提供了大部分内容。

int main() {
    std::unique_ptr<X> ptr{new X{}};
    return 0;
    //Destructor called on *ptr
    //`delete` called on ptr
}

这在您的原始类(class)中是个好主意,但需要注意的是,除非您明确更改它,否则您的类(class)将不再可复制(尽管它仍然是可移动的):

class X {
    std::unique_ptr<int> ptr;
public:
    X() {
        ptr.reset(new int{0});
    }
    X(int val) {
        ptr.reset(new int{val});
    }
    //X(X && x); //auto generated by compiler
    //X & operator=(X && x); //auto generated by compiler
    //~X() noexcept; //auto generated by compiler

    //X(X const& x); //Deleted by compiler
    //X & operator=(X const& x); //Deleted by compiler
};

我们可以看到我以前版本的main的变化:

int main() {
    X a; //Default-Constructor
    //X b = a; //Was Copy-Constructor, no longer compiles
    X c(5); //Other Constructor
    X d{7}; //Also Other Constructor
    X f{}; //Default-Constructor
    X g = X(8); //Other Constructor (uses copy-elision to avoid calling Other + move-assign + Destructor)
    X h = std::move(c); //Move-Constructor (uses copy-elision to avoid calling Default + move-assign)
    //b = c; //Was Copy-assignment, no longer compiles
    c = std::move(d); //Move-assignment
    d = X{15}; //Other Constructor, then Move-Assignment, then Destructor on `X{15}`.
    return 0;
    //Destructor on `h`
    //Destructor on `g`
    //Destructor on `f`
    //Destructor on `d`
    //Destructor on `c`
    //Destructor on `a`
}

如果你想使用 std::unique_ptr,但也希望生成的类是可复制的,你需要使用我讨论的技术自己实现复制构造函数。

应该就这些了!如果我遗漏了什么,请告诉我。

关于c++ - 如果将变量设置为新对象,旧对象会发生什么情况?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44707245/

有关c++ - 如果将变量设置为新对象,旧对象会发生什么情况?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  5. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  6. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  7. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  10. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

随机推荐