链接分配运算符无需使用C+&#x2B中的参考文献而无需使用参考。

发布于 2025-01-29 02:10:40 字数 2128 浏览 1 评论 0原文

我写了此代码

#include<iostream>
using namespace std;
class circle
{   public:
        int radius;
    public:
        circle()
        {   cout<<"object address-"<<this<<endl;
        }
        circle(int r)
        {   radius=r;
            cout<<"object address-"<<this<<" and radius is "<<this->radius<<endl;
        }
        circle operator = (circle c)
        {   cout<<"my object is "<<this<<endl;
            cout<<"my arg is "<<&c<<endl;
            radius=c.radius;
            return circle(radius);
        }
};
int main()
{   cout<<"creating circle c1 with radius 10"<<endl;
    circle c1(10);
    cout<<"creating empty circle c2"<<endl;
    circle c2;
    cout<<"creating empty circle c3"<<endl;
    circle c3;
    cout<<"c3=c2=c1"<<endl;
    c3=c2=c1;
    cout<<"Final values"<<endl<<"c1 radius-"<<c1.radius<<endl<<"c2 radius-"<<c2.radius<<endl<<"c3 radius-"<<c3.radius<<endl;
    return 0;
}

输出

creating circle c1 with radius 10
object address-0xffffcbfc and radius is 10
creating empty circle c2
object address-0xffffcbf8
creating empty circle c3
object address-0xffffcbf4
c3=c2=c1
my object is 0xffffcbf8
my arg is 0xffffcbd8
object address-0xffffcbbc and radius is 10
my object is 0xffffcbf4
my arg is 0xffffcbd8
object address-0xffffcbbc and radius is 10
Final values
c1 radius-10
c2 radius-10
c3 radius-10

似乎是在起作用。

问题1)首选使用参考来避免创建变量的副本。我可以说我的程序还不错,但效率不高?
问题2)请指出我理解的缺陷。
我认为以下是发生
0xfffffcbd8是0xfffffcbfc(Circle C1)
的副本 第一个C2.操作器=(C1的副本),然后将C2的半径设置为C1的半径,并返回新对象0xfffffcbbc。
我希望现在在-c3.operator =(某个圆)
中 一些圆圈应该是0xfffffcbbc
的副本 但是,Circle C1的原始副本似乎是“一些圆圈”,这使我认为C3.Operator =(C1的副本)正在发生。那是对的吗?
似乎在任何地方都没有使用分配运算符的返回值。为什么?

谢谢。

PS:请注意,我知道“ circle&amp; operator =(circle&amp; c)”,而不是“ circle operator =(circle c)”和“ return *this”,而不是“返回圆圈(radius)”会使它变得还不错。但是我想了解我的代码中的行为和缺陷。谢谢 。

I wrote this code

#include<iostream>
using namespace std;
class circle
{   public:
        int radius;
    public:
        circle()
        {   cout<<"object address-"<<this<<endl;
        }
        circle(int r)
        {   radius=r;
            cout<<"object address-"<<this<<" and radius is "<<this->radius<<endl;
        }
        circle operator = (circle c)
        {   cout<<"my object is "<<this<<endl;
            cout<<"my arg is "<<&c<<endl;
            radius=c.radius;
            return circle(radius);
        }
};
int main()
{   cout<<"creating circle c1 with radius 10"<<endl;
    circle c1(10);
    cout<<"creating empty circle c2"<<endl;
    circle c2;
    cout<<"creating empty circle c3"<<endl;
    circle c3;
    cout<<"c3=c2=c1"<<endl;
    c3=c2=c1;
    cout<<"Final values"<<endl<<"c1 radius-"<<c1.radius<<endl<<"c2 radius-"<<c2.radius<<endl<<"c3 radius-"<<c3.radius<<endl;
    return 0;
}

Output is

creating circle c1 with radius 10
object address-0xffffcbfc and radius is 10
creating empty circle c2
object address-0xffffcbf8
creating empty circle c3
object address-0xffffcbf4
c3=c2=c1
my object is 0xffffcbf8
my arg is 0xffffcbd8
object address-0xffffcbbc and radius is 10
my object is 0xffffcbf4
my arg is 0xffffcbd8
object address-0xffffcbbc and radius is 10
Final values
c1 radius-10
c2 radius-10
c3 radius-10

It seems that it is working.

Question 1 ) Using references is preferred to avoid creating copy of variables.Can I say that my program is alright but not efficient ?
Question 2 ) Please point out flaw in my understanding .
I think following happens
0xffffcbd8 is a copy of 0xffffcbfc (circle c1)
First c2.operator=(copy of c1) happens then radius of c2 is set to that of c1 and new object 0xffffcbbc is returned .
I was hoping that now in - c3.operator=(some circle)
some circle should be a copy of 0xffffcbbc
But , it seems original copy of circle c1 is 'some circle' which leads me to think that c3.operator=(copy of c1) is happening . Is that correct ?
It seems return value of assignment operator is not being used anywhere . Why is that ?

Thanks.

PS: please note that I know that "circle& operator = (circle& c)" instead of "circle operator = (circle c)" and "return *this" instead of "return circle(radius)" will make it alright . But I want to understand the behaviour and flaw in my code . Thanks .

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

眼眸 2025-02-05 02:10:40

​有规则,当这些规则是自动生成的。有些是精心选择的,有些是有历史原因。

在桌子的左侧,如果您自己声明了其中一些,则看到其他特殊成员功能会发生什么。对于特殊成员函数,您可以编写实现或说= delete= = = = default在类定义中。该表显示了编译器隐式声明为默认或已删除的其他哪个表。

C ++中有三/五/零的重要规则:您通常必须实现(或单独声明为已删除)特殊成员函数的三个,五个或零(默认构造函数除外): https://en.cppreference.com/w/cpp/language/rule_of_three
这不是固定的规则,而是经验法则,通常指向代码中的错误,如果不遵循该错误。

例如,如果您进行手动资源管理(例如打开文件或分配内存),则必须在副本和破坏过程中处理。创建两个动作功能是可选的,可以添加性能改进(例如,仅移动文件句柄而不是实际重新打开文件)。

特殊成员功能具有典型的签名,但是您可以偏离它。典型的(自动生成)签名的典型签名是:

T(); // default constructor
T(const T&); // copy constructor
T(T&&); // move constructor
T& operator=(const T&); // copy assignment
T& operator=(T&&); // move assignment
virtual ~T(); // destructor

创建不同的签名会导致性能或意外功能,但它是完全合法的C ++,有时可以使用。

浏览您在Pastebin上发布的程序输出,

复制构造函数是自动生成的,并且可以在后台工作,您可以使用控制台输出添加它以获得更完整的图片,即正在发生的情况。

creating circle c1 with radius 10
object address-0x7ffc542431f0 and radius is 10
 -> the circle(int) constructor is called with radius 10

creating empty circle c2
object address-0x7ffc54243200
 -> the circle() default constructor is called, the radius is unspecified (as no default was set in the class definition nor in a member initialization list of the constructor)

creating empty circle c3
object address-0x7ffc54243210
 -> the circle() default constructor is called, the radius is unspecified

c3=c2=c1
 -> this acts like c3=(c2=c1), the right assignment is executed first

 -> in the background the copy constructor for a new circle is called (as the parameter is a value and not a reference, so we have to create the parameter as separate object) and copies c1 into a new temporary circle, the new circle has address 0x7ffc54243220 and radius 10

my object is 0x7ffc54243200
my arg is 0x7ffc54243220
 -> the copy assignment operator is called for c2 with the temporary circle object as parameter

object address-0x7ffc54243230 and radius is 20
 -> the circle(int) constructor is called from within the assignment operator to create a temporary circle with radius 20 (`return circle(20)` line)

my object is 0x7ffc54243210
my arg is 0x7ffc54243230
 -> the copy assignment operator is called on c3 with the temporary circle created in the previous return line

object address-0x7ffc54243240 and radius is 20
 -> a new temporary circle is created, which is just discarded afterwards

in destructor for object 0x7ffc54243240
in destructor for object 0x7ffc54243230
in destructor for object 0x7ffc54243220
 -> the temporary objects are destroyed

Final values
c1 radius-10
 -> c1 was not modified after destruction

c2 radius-10
 -> c2 got assigned the radius 10 from the temporary circle constructed from c1

c3 radius-20
 -> c3 got assigned the radius 20 from the temporary circle constructed in the return from the (right) assignment operator

in destructor for object 0x7ffc54243210
in destructor for object 0x7ffc54243200
in destructor for object 0x7ffc542431f0
 -> c1, c2 and c3 are destroyed at the end of main

优化器可以删除许多用于创建临时对象的代码,如果类简单,如果可以看到代码(如果库中的圆圈不起作用,如果circle在库中),则在选项中激活了整个程序优化或链接时间优化(或您的函数在类定义中称为内联或定义,就像您的代码那样而不是单独的.cpp file =翻译单位)。如果将控制台输出或其他非平凡代码放在特殊成员功能中 - 优化器别无选择 - 必须按顺序调用它们。

c ++见解

带有C ++洞察力( c ++ insights链接到您的代码)输出编译器的大量自动代码生成:

  • 构件函数均声明为inline,因为它们在您的类定义中定义而不是单独的翻译单元。

  • 在第31行中,临时圆的生成被包装到另一个复制构造函数(Inner circle(20),如您的代码中所写,外部Circle用于创建参数):返回圆圈(Circle(20));自动删除此双重创建。它被称为 copy elision https://en.cppreference .com/w/w/cpp/language/copy_elision ),在某些情况下标准规定,其中一个正在返回一个值时,可以直接使用它。

  • 在第34行中,编译器自动生成默认复制构造函数// inline constexpr circle(const circle&amp;)noexcept = default;

  • 在第43行和45中,您会看到默认构造函数被称为<<代码> Circle C2 = Circle();

  • 在第47行中,您会看到添加的paranthess和临时circle out创建c1,因为复制分配的参数是值,而不是参考:c3.operator =(c2.operator =(circle(c1)));

Auto-Generation of special member functions

C++ has several special member functions for construction, assignment and destruction. There are rules, when those are auto-generated. Some are carefully chosen, some have historical reasons.

On the left side of the table you see, what happens to the other special member functions, if you declare some of those yourself. For the special member functions you can write an implementation or say = deleteor = default in the class definition. The table shows, which of the other ones the compiler implicitly declares as defaulted or as deleted.

There is the important rule of three/five/zero in C++: You typically have to either implement (or individually declare as deleted) three, five or zero of the special member functions (except the default constructor): https://en.cppreference.com/w/cpp/language/rule_of_three
This is no fixed rule, more a rule of thumb, often pointing to bugs in the code, if it is not followed.

E.g. if you do manual resource management (e.g. opening files or allocating memory), then this has to be handled during copy as well as during destruction. Creating the two move functions is optional and can add a performance improvement (e.g. by just moving the file handle instead of actually reopening files).

The special member functions have a typical signature, but you can deviate from it. The typical (and default for auto-generated) signatures are:

T(); // default constructor
T(const T&); // copy constructor
T(T&&); // move constructor
T& operator=(const T&); // copy assignment
T& operator=(T&&); // move assignment
virtual ~T(); // destructor

Creating a different signature can lead to worse performance or unexpected functionality, but it is perfectly legal C++ and can have uses sometimes.

Going through the program output you posted on pastebin

The copy constructor is auto-generated and works in the background, you could add it with console output to get an even more complete picture, what is happening.

creating circle c1 with radius 10
object address-0x7ffc542431f0 and radius is 10
 -> the circle(int) constructor is called with radius 10

creating empty circle c2
object address-0x7ffc54243200
 -> the circle() default constructor is called, the radius is unspecified (as no default was set in the class definition nor in a member initialization list of the constructor)

creating empty circle c3
object address-0x7ffc54243210
 -> the circle() default constructor is called, the radius is unspecified

c3=c2=c1
 -> this acts like c3=(c2=c1), the right assignment is executed first

 -> in the background the copy constructor for a new circle is called (as the parameter is a value and not a reference, so we have to create the parameter as separate object) and copies c1 into a new temporary circle, the new circle has address 0x7ffc54243220 and radius 10

my object is 0x7ffc54243200
my arg is 0x7ffc54243220
 -> the copy assignment operator is called for c2 with the temporary circle object as parameter

object address-0x7ffc54243230 and radius is 20
 -> the circle(int) constructor is called from within the assignment operator to create a temporary circle with radius 20 (`return circle(20)` line)

my object is 0x7ffc54243210
my arg is 0x7ffc54243230
 -> the copy assignment operator is called on c3 with the temporary circle created in the previous return line

object address-0x7ffc54243240 and radius is 20
 -> a new temporary circle is created, which is just discarded afterwards

in destructor for object 0x7ffc54243240
in destructor for object 0x7ffc54243230
in destructor for object 0x7ffc54243220
 -> the temporary objects are destroyed

Final values
c1 radius-10
 -> c1 was not modified after destruction

c2 radius-10
 -> c2 got assigned the radius 10 from the temporary circle constructed from c1

c3 radius-20
 -> c3 got assigned the radius 20 from the temporary circle constructed in the return from the (right) assignment operator

in destructor for object 0x7ffc54243210
in destructor for object 0x7ffc54243200
in destructor for object 0x7ffc542431f0
 -> c1, c2 and c3 are destroyed at the end of main

The optimizer can remove a lot of code for creating temporary objects, if the classes are simple, if it can see the code (does not work, if circle is in a library) and whole program optimization or link time optimization is activated in the options (or your functions are declared as inline or defined in your class definition as is the case with your code instead of a separate .cpp file=translation unit). If you put console output or other non-trivial code in the special member functions - the optimizer has no choice - they have to be called in order.

C++ Insights

With C++ Insights (C++ Insights Link to your code) you can output a lot of the automatic code generation of the compiler:

  • The member function are all declared as inline as they are defined in your class definition instead of a separate translation unit.

  • In line 31 the generation of the temporary circle is wrapped into another copy constructor (the inner circle(20) as written in your code and the outer circle for creation of the parameter): return circle(circle(20)); This double creation is removed automatically. It is called copy elision (https://en.cppreference.com/w/cpp/language/copy_elision), which is mandated by the standard in some situations, one of those is returning a value, when it can be directly used.

  • In line 34 the compiler auto-generates the default copy constructor // inline constexpr circle(const circle &) noexcept = default;

  • In lines 43 and 45 you see the default constructor being called circle c2 = circle();

  • In line 47 you see the parantheses being added and the temporary circle out of c1 being created, as the parameter of the copy assignment is a value instead of a reference: c3.operator=(c2.operator=(circle(c1)));

南笙 2025-02-05 02:10:40

如果“好的”您的意思是“作为一个毫无戒心的用户会期望”,那就不正确。 operator =常规返回*此是有原因的,而该原因是不是效率。缺乏惊喜。在C ++中,我们希望用户定义的运算符应以类似于内置运算符的方式行事。您的作业没有。

int x = 1, y = 2, z = 3;
(x = y) = z;  // x == 3, y == 2, z == 3

circle p(1), q(2), r(3);
(p = q) = r; // actual   : p.radius == 2, q.radius == 2, r.radius == 3
             // expected : p.radius == 3, q.radius == 2, r.radius == 3

当然,(p = q)= r之类的东西在实际代码中很少见,但这只是一个测试案例,您的实现失败了 sility (即没有任何错误消息)。

它不是违法的,但这是出乎意料的,您需要一个非常有充分的理由来产生做意外事件的组件。据我所知,您没有一个。

人们可以并且确实可以修改分配的结果,他们通常希望这些修改应用于分配的对象,而不是将这些修改立即消失。

If by "alright" you mean "behaves as an unsuspecting user would expect", then no, it is not alright. There is a reason behind operator= conventionally returning *this by reference, and that reason is not efficiency. It is lack of surprise. In C++, we expect that user-defined operators should behave in a manner similar to built-in operators. Your assignment doesn't.

int x = 1, y = 2, z = 3;
(x = y) = z;  // x == 3, y == 2, z == 3

circle p(1), q(2), r(3);
(p = q) = r; // actual   : p.radius == 2, q.radius == 2, r.radius == 3
             // expected : p.radius == 3, q.radius == 2, r.radius == 3

Granted, things like (p = q) = r are rare in actual code, but this is just a test case, which your implementation fails silently (i.e. with no error message).

It is not illegal, but it is unexpected, and you need a very, very good reason to produce a component that does unexpected things. As far as I can see, you don't have one.

People can and do modify the result of assignment, and they usually expect that these modifications are applied to the assigned-to object, rather than to some temporary that will go away immediately.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文