为什么在堆上分配对象时不调用复制构造函数?
Why is the copy constructor not called when objects are allocated on heap?
class Guitars
{
private:
int serialNumber{0};
float price{0.0};
// GuitarSpecs spec{};
public:
Guitars(int serNum, float price)
{
this->serialNumber = serNum;
this->price = price;
};
Guitars(const Guitars &s)
: serialNumber{s.serialNumber}, price{s.price}
{
std::cout << "Copy" << std::endl;
};
Guitars(Guitars &&source) noexcept : serialNumber{source.serialNumber}, price{source.price}
{
source.serialNumber = NULL;
source.price = NULL;
std::cout << "Move" << std::endl;
};
int GetSerial() const { return serialNumber; };
float GetPrice() const { return price; };
void SetPrice(float x) { this->price = x; }
};
class Inventory
{
private:
list<Guitars *> *guitarList;
public:
Inventory()
{
guitarList = new list<Guitars *>;
}
void AddGuitar(int serNum, float price)
{
Guitars *x = new Guitars(serNum, price);
// Guitars x(serNum,price);
guitarList->push_back(x);
}
void Display()
{
for (auto &&i : *guitarList)
{
std::cout << i->GetPrice() << " " << i->GetSerial() << endl;
}
}
~Inventory()
{
for (auto &&i : *guitarList)
{
std::cout << i->GetSerial() << " "
<< "deleted " << std::endl;
delete i;
}
std::cout << "List is deleted" << std::endl;
delete guitarList;
}
};
int main()
{
Inventory I;
I.AddGuitar(12050, 50.23);
I.AddGuitar(10000, 20.00);
I.Display();
return 0;
}
谁能给我解释一下为什么上面的代码没有调用拷贝构造函数?
当我在堆上创建一个 Guitar 指针列表以及一个指向它们的 Guitar 对象并将这些指针保存在 Inventory 列表中时,不会调用复制构造函数。为什么会发生这种情况,这个程序是否更有效率,因为程序不会创建对象的副本,它在堆上创建一次,我们会保存指针。
一些详细的答案,基于你所说的优化:
您有以下代码:
list<Guitars *> *guitarList;
void AddGuitar(int serNum, float price)
{
Guitars *x = new Guitars(serNum, price);
// Guitars x(serNum,price);
guitarList->push_back(x);
}
我认为您使用所有这些指针的原因是为了更快。如果像往常一样在堆栈上创建一个 Guitars
对象,然后将其推回,则会创建一个副本,true。
您可以做的是为 Guitars
定义一个移动操作,并将堆栈对象移动到您在列表中创建的对象中,例如调用 move constructor.
但更好的方法是使用 std::list::emplace_back
,像这样:
list<Guitars> guitarList;
void AddGuitar(int serNum, float price)
{
guitarList.emplace_back(serNum, price);
}
无论如何,如果你谈论最优性,那些指针是不好的。指针需要额外的space,并且每次访问数据时,都必须取消引用指针。此外,正如@PaulMcKenzie 在评论中所写,这会阻止编译器为您优化。
此外,使列表成员本身成为指针,即与 list<Guitars*>* guitarList;
或 list<Guitars>* guitarList;
一起使用,也不是一个好主意。我看到的唯一原因是,如果您想交换两个 Inventory
对象的列表,但在这种情况下,只需在列表上调用 std::swap
。
如果您放下指针,请注意您的所有其他代码会立即变得更加容易。您甚至根本不必定义析构函数。
(至于你问的实际问题,就像@Jarod42 已经写的那样,复制指针不会复制对象。)
(顺便说一句,如果 class Guitars
代表一把吉他,那么我会选择单数,Guitar
。)
编辑:
我用不同的方式创建了一系列测试来填充列表,使用 Guitars
大部分未修改。 (虽然我删除了非指针对 NULL
的分配。)无论如何,我做了以下测试设置:
#include <iostream>
#include <list>
class Guitar
{
private:
int serialNumber{0};
float price{0.0};
public:
Guitar(int serNum, float price)
{
std::cout << "Base" << std::endl;
this->serialNumber = serNum;
this->price = price;
}
Guitar(const Guitar& s)
: serialNumber{s.serialNumber}, price{s.price}
{
std::cout << "Copy" << std::endl;
}
Guitar(Guitar&& source) noexcept : serialNumber{source.serialNumber}, price{source.price}
{
std::cout << "Move" << std::endl;
}
};
void test_1()
{
std::cout << "test 1" << std::endl;
std::list<Guitar*> guitarList;
Guitar* x = new Guitar(1, 2.);
guitarList.push_back(x);
std::cout << std::endl;
}
void test_2()
{
std::cout << "test 2" << std::endl;
std::list<Guitar> guitarList;
Guitar x(1, 2.);
guitarList.push_back(x);
std::cout << std::endl;
}
void test_3()
{
std::cout << "test 3" << std::endl;
std::list<Guitar> guitarList;
guitarList.push_back(Guitar(1, 2.));
std::cout << std::endl;
}
void test_4()
{
std::cout << "test 4" << std::endl;
std::list<Guitar> guitarList;
guitarList.emplace_back(1, 2.);
std::cout << std::endl;
}
int main()
{
test_1();
test_2();
test_3();
test_4();
}
这个输出是:
test 1
Base
test 2
Base
Copy
test 3
Base
Move
test 4
Base
我希望这能进一步了解这里的工作原理。
测试可以在 http://www.cpp.sh/35ld6
下找到
另外,我想提一下,如果我们谈论优化,我们就必须谈论我们优化了什么。现在,我们有几乎没有内容的小对象列表。在那种情况下,根本不会优化,因为我们谈论的是纳秒级的差异。
要考虑的案例是:
- 一小列大物件,方便移动。在那种情况下,我们需要确保没有调用复制构造函数,但是 move 就可以了。
- 一小列大物难移。在那种情况下,我们只想使用基本运算符,可能像您最初那样使用指针 - 但
emplace_back
也可以使事情变得更容易。请注意,难以移动的对象暗示 class. 的设计不佳
- 一大堆小物件。这里我们希望使用尽可能少的构造函数,包括移动构造函数。我们也不想使用指针列表,因为这会给我们每个对象额外的 64 位,并在以后进行大量取消引用。那样的话,
emplace_back
真是大放异彩。
换句话说,emplace_back
不会出错。
class Guitars
{
private:
int serialNumber{0};
float price{0.0};
// GuitarSpecs spec{};
public:
Guitars(int serNum, float price)
{
this->serialNumber = serNum;
this->price = price;
};
Guitars(const Guitars &s)
: serialNumber{s.serialNumber}, price{s.price}
{
std::cout << "Copy" << std::endl;
};
Guitars(Guitars &&source) noexcept : serialNumber{source.serialNumber}, price{source.price}
{
source.serialNumber = NULL;
source.price = NULL;
std::cout << "Move" << std::endl;
};
int GetSerial() const { return serialNumber; };
float GetPrice() const { return price; };
void SetPrice(float x) { this->price = x; }
};
class Inventory
{
private:
list<Guitars *> *guitarList;
public:
Inventory()
{
guitarList = new list<Guitars *>;
}
void AddGuitar(int serNum, float price)
{
Guitars *x = new Guitars(serNum, price);
// Guitars x(serNum,price);
guitarList->push_back(x);
}
void Display()
{
for (auto &&i : *guitarList)
{
std::cout << i->GetPrice() << " " << i->GetSerial() << endl;
}
}
~Inventory()
{
for (auto &&i : *guitarList)
{
std::cout << i->GetSerial() << " "
<< "deleted " << std::endl;
delete i;
}
std::cout << "List is deleted" << std::endl;
delete guitarList;
}
};
int main()
{
Inventory I;
I.AddGuitar(12050, 50.23);
I.AddGuitar(10000, 20.00);
I.Display();
return 0;
}
谁能给我解释一下为什么上面的代码没有调用拷贝构造函数?
当我在堆上创建一个 Guitar 指针列表以及一个指向它们的 Guitar 对象并将这些指针保存在 Inventory 列表中时,不会调用复制构造函数。为什么会发生这种情况,这个程序是否更有效率,因为程序不会创建对象的副本,它在堆上创建一次,我们会保存指针。
一些详细的答案,基于你所说的优化:
您有以下代码:
list<Guitars *> *guitarList;
void AddGuitar(int serNum, float price)
{
Guitars *x = new Guitars(serNum, price);
// Guitars x(serNum,price);
guitarList->push_back(x);
}
我认为您使用所有这些指针的原因是为了更快。如果像往常一样在堆栈上创建一个 Guitars
对象,然后将其推回,则会创建一个副本,true。
您可以做的是为 Guitars
定义一个移动操作,并将堆栈对象移动到您在列表中创建的对象中,例如调用 move constructor.
但更好的方法是使用 std::list::emplace_back
,像这样:
list<Guitars> guitarList;
void AddGuitar(int serNum, float price)
{
guitarList.emplace_back(serNum, price);
}
无论如何,如果你谈论最优性,那些指针是不好的。指针需要额外的space,并且每次访问数据时,都必须取消引用指针。此外,正如@PaulMcKenzie 在评论中所写,这会阻止编译器为您优化。
此外,使列表成员本身成为指针,即与 list<Guitars*>* guitarList;
或 list<Guitars>* guitarList;
一起使用,也不是一个好主意。我看到的唯一原因是,如果您想交换两个 Inventory
对象的列表,但在这种情况下,只需在列表上调用 std::swap
。
如果您放下指针,请注意您的所有其他代码会立即变得更加容易。您甚至根本不必定义析构函数。
(至于你问的实际问题,就像@Jarod42 已经写的那样,复制指针不会复制对象。)
(顺便说一句,如果 class Guitars
代表一把吉他,那么我会选择单数,Guitar
。)
编辑:
我用不同的方式创建了一系列测试来填充列表,使用 Guitars
大部分未修改。 (虽然我删除了非指针对 NULL
的分配。)无论如何,我做了以下测试设置:
#include <iostream>
#include <list>
class Guitar
{
private:
int serialNumber{0};
float price{0.0};
public:
Guitar(int serNum, float price)
{
std::cout << "Base" << std::endl;
this->serialNumber = serNum;
this->price = price;
}
Guitar(const Guitar& s)
: serialNumber{s.serialNumber}, price{s.price}
{
std::cout << "Copy" << std::endl;
}
Guitar(Guitar&& source) noexcept : serialNumber{source.serialNumber}, price{source.price}
{
std::cout << "Move" << std::endl;
}
};
void test_1()
{
std::cout << "test 1" << std::endl;
std::list<Guitar*> guitarList;
Guitar* x = new Guitar(1, 2.);
guitarList.push_back(x);
std::cout << std::endl;
}
void test_2()
{
std::cout << "test 2" << std::endl;
std::list<Guitar> guitarList;
Guitar x(1, 2.);
guitarList.push_back(x);
std::cout << std::endl;
}
void test_3()
{
std::cout << "test 3" << std::endl;
std::list<Guitar> guitarList;
guitarList.push_back(Guitar(1, 2.));
std::cout << std::endl;
}
void test_4()
{
std::cout << "test 4" << std::endl;
std::list<Guitar> guitarList;
guitarList.emplace_back(1, 2.);
std::cout << std::endl;
}
int main()
{
test_1();
test_2();
test_3();
test_4();
}
这个输出是:
test 1
Base
test 2
Base
Copy
test 3
Base
Move
test 4
Base
我希望这能进一步了解这里的工作原理。
测试可以在 http://www.cpp.sh/35ld6
另外,我想提一下,如果我们谈论优化,我们就必须谈论我们优化了什么。现在,我们有几乎没有内容的小对象列表。在那种情况下,根本不会优化,因为我们谈论的是纳秒级的差异。
要考虑的案例是:
- 一小列大物件,方便移动。在那种情况下,我们需要确保没有调用复制构造函数,但是 move 就可以了。
- 一小列大物难移。在那种情况下,我们只想使用基本运算符,可能像您最初那样使用指针 - 但
emplace_back
也可以使事情变得更容易。请注意,难以移动的对象暗示 class. 的设计不佳
- 一大堆小物件。这里我们希望使用尽可能少的构造函数,包括移动构造函数。我们也不想使用指针列表,因为这会给我们每个对象额外的 64 位,并在以后进行大量取消引用。那样的话,
emplace_back
真是大放异彩。
换句话说,emplace_back
不会出错。