Getter & Setter 的最佳性能
Best Performance for Getter & Setter
最近我使用getter和setter方法进行了一些性能测试。我仍然不确定在c++中使用它们的最佳方法。(使用int等小类型没有区别)
string i;
string GetTest()
{
return i;
}
void SetTest(string i)
{
this->i = i; //copy
}
这就是我在java/C#中使用它的方式。没有引用/指针,而且在c++中速度非常慢。对于字符串、矢量和其他"大"类型,如果频繁调用,这是非常缓慢和糟糕的
string i;
const string& GetTest()
{
return i;
}
void SetTest(const string& i)
{
this->i = i; //copy
}
目前我正在使用上面的这个版本,它比不使用参数和返回值的引用快得多,而且使用const,我确保它不会被更改。
string* i;
string& GetTest()
{
return *i;
}
void SetTest(string& i)
{
this->i = &i;
}
这个版本,使用指针比只使用引用更快,但我认为它更难阅读,如果你想使用类中的值,你必须使用指针。
除了使用引用/指针之外,它还可以通过内联经常被称为getter&setter,因为它们是小函数。
inline GetTest(){....}
inline SetTest(){....}
但如前所述,我仍然不确定在c++中使用它们的最佳方式,或者是否还有其他方式。消除这些歧义是件好事。
编辑:
时隔近4年,以下是我现在对现代C++的建议:(比任何文本都更有帮助)
https://www.youtube.com/watch?v=xnqTKD8uD6451:00
我建议你看完整的视频,赫伯·萨特是一个如此了不起的演说家,一般都会关注CppCon。
[我完全从我之前在Simple C++getter/setters上的回答中复制了第一段]通常使用访问器/赋值器是一种设计气味,即类的公共接口是不完整的。通常来说,你想要一个有用的公共接口,它提供有意义的功能,而不是简单地获取/设置(这比我们在C中使用结构和函数要好一两步)。每次你想写一个赋值函数,很多时候你想先写一个访问器,只需退一步问问自己"我真的需要这个吗?"。
现在,如果您真的想为特定的属性设置getter/setter:前两个版本中的任何一个都是正确的,尽管第二个版本在某些情况下可能会更好。getter应该是const
(const string& GetTest() const
),这样就可以在const对象上调用它们。
你的第三个版本的指针是非常危险的。虽然前两个具有非常清晰的所有权语义,但第三个没有。您所显示的没有清理的简短片段表明,set函数的调用方必须在类的持续时间内保持字符串的有效性(没有所有权转移)。完全避免这种构造。
最后,如果你只是在学习C++,请考虑C++图书列表中的一本或多本书。最终C++图书指南和列表
编辑:如果你想提高代码的性能,最好的方法是通过优化编译,然后分析并查看热点在哪里。
如果您被限制为C++98/C++03,那么您的第二个代码确实是最佳解决方案。第三段代码很容易出错,而且是非二进制的,如果通过使用指针或更好的智能指针作为参数来解决这个问题,那仍然不理想(而且你还必须处理字符串的内存管理)。
如果您可以使用C++11或C++14,那么对于间接保存其数据并实现移动语义(如std::string
)的类型,最佳的setter实现是
void SetTest(std::string a_i) // by value!
{
i = std::move(a_i); // move assignment
}
这之所以是最优的,是因为对于左值参数,它的成本大约与引用实现一样高(因为std::move
防止复制实际数据),但对于右值参数(例如另一个函数的返回值,或者当显式使用std::move
时),参数也是移动构造的,因此,在这种情况下,可以避免所有副本,并且与第三个解决方案一样高效,不会出现问题。
请注意,对于大尺寸的类型(如具有许多成员的structs),已经很浅的副本将是昂贵的,因此即使在C++11中,您的第二个版本仍然是此类类型的最佳版本。对于这样的类型,除非类额外管理分配的资源,否则附加的右值版本将不会提高性能。
当然,大多数时候,正确的解决方案是根本没有getter/setter。
你在问题中列出的这三个版本做了三件完全不同的事情,这就是为什么它们以不同的速度运行。在C++中执行字符串getter和setter的"正确"方法是:
std::string _test;
const std::string& GetTest() const
{
return _test;
}
void SetTest(const std::string& test)
{
this->_test = test; //copy
}
//optional, not necessary
void SetTest(std::string&& test)
{
this->_test = std::move(test);
}
不管其他贡献者怎么说,getter和setter并不一定是糟糕的类设计。它们通常比直接访问成员变量更可取。然而,过度使用Getters/Setters(或直接访问成员变量)是一种代码气味。
您的第一个版本是可靠和正确的(除了Getter不是const
,但它要求编译器在各种情况下不必要地复制和销毁字符串:
string GetTest()
{
return i;
}
void SetTest(string i)
{
this->i = i; //copy
}
...
// this is slower than the "right" way, because it requires creating a copy of the string and destroying it:
bool isEmpty = something.GetTest().size() != 0; // the "right" way does not create a copy here.
// this is slower than the "right way, because it requires creating a copy of the string and destroying it:
std::string myString("I am your father, Luke.");
something.SetTest(myString); // this copies "myString", then calls SetTest with the copy, then destroys the copy
你建议的第三个版本应该不惜一切代价避免,因为它是一个脆弱的设计,肯定会导致意想不到的错误:
string* i;
string& GetTest()
{
return *i;
}
void SetTest(string& i)
{
this->i = &i;
}
// this modifies the string after the setter
std::string* myString = new std::string("I am your father, Luke.");
someting.SetTest(*myString);
myString->assign("Oranges");
// now this is true: someting.GetTest() == "Oranges"
delete myString;
// now this is undefined behaviour: someting.GetTest() == "Oranges"
如果您真的真的需要类似java/c#的行为,那么执行第三个版本的"正确"方法如下:
std::shared_ptr<const std::string> _test;
const std::shared_ptr<const std::string>& GetTest() const
{
return _test;
}
void SetTest(const std::shared_ptr<const std::string>& test)
{
this->_test = test;
}
void SetTest(std::shared_ptr<const std::string>&& test)
{
this->_test = std::move(test);
}
如果将指向数据的指针存储在getter和setter中,则所有权和对象生存期将出现重大问题。Jave和C#对此有自己的处理方法。在C++中,您需要使用各种各样的智能指针(在这种情况下可能是shared_ptr
,但最好的指南是测试您获得的实际性能)。
对于大多数C++编译器,无论对它们做什么,小的get/set方法都将内联。如果在头文件中定义实现,那么它们肯定会内联!
唯一的例外是,如果它们被构建到共享库中(这很难内联到调用方的代码中),但即使这样,您也可以在现代编译器中使用链接时内联进行编译,然后也会在那里内联!
即使是复杂的类型,也有一种叫做"返回值优化"(或RVO)的东西,即您的返回类型被编译为使用与调用方使用的类型相同的存储,即整个副本被优化掉。
诀窍是让编译器通过保持代码简单来完成它的任务。测试代码的性能是可能的,但我认为你无论如何都会在其他领域调整性能。
PS。getter和setter。。。它们的设计很糟糕。不要鼓励他们。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 在C#中处理C++指针而不使用unsafe的最佳方法
- OpenMP阵列性能较差
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 递归列出所有目录中的C++与Python与Ruby的性能
- 在c代码之间共享数据的最佳方式
- C++ 将函数指针与最佳性能相结合
- 从 {fmt} 获得最佳性能
- 只在模板中专门化构造函数,保持最佳性能和整洁的界面
- C 初始化列表std ::数组最佳性能
- 具有任意基数的基本任意精度算术的最佳性能
- C# 与 C/C++:我是否需要手动对结构字段进行排序以获得最佳性能
- 为什么缓冲区应该在 64 字节边界上对齐以获得最佳性能
- Getter & Setter 的最佳性能
- 排序列表的最佳数据结构(性能)
- 查找仅在性能测试下发生的堆损坏的最佳方法是什么?
- 在保持良好的面向对象设计的同时重用代码以提高性能的最佳方法是什么?
- 如何将 Argb32 加载到特征矩阵中以获得最佳性能
- 测试值是否在阈值范围内的最佳方法(性能方面)是什么