【问题标题】:Best Performance for Getter & SetterGetter & Setter 的最佳性能
【发布时间】:2014-11-03 01:27:35
【问题描述】:

最近我使用 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

我建议观看完整视频,Herb Sutter 如此出色的演讲者,一般关注 CppCon。

【问题讨论】:

  • 带有this->i = &i;的版本非常错误。您保留指向您一无所知的对象的指针。我建议忘记那些优化,先更好地学习 C++。
  • 做性能测试!不要进行过早的优化!
  • 如果你想(完全)很好地使用 C++,你需要摆脱 Java/C# 的习惯用法,比如到处使用 getter 和 setter。在 C++ 中,我们做的事情略有不同:我们致力于理解真正的问题并设计代码以很好地解决它——而实际上从不涉及使用 getter 或 setter。
  • 通常的做法是在带有类声明的头文件中定义此类琐碎的单行函数。这将使它们隐含地inline
  • @R.MartinhoFernandes 纠正*输出垃圾的快速程序。

标签: c++ performance getter-setter


【解决方案1】:

如果您将指向数据的指针存储在您的 getter 和 setter 中,您将在所有权和对象生命周期方面遇到重大问题。 Jave 和 C# 有自己的处理方式。在 C++ 中,您需要使用各种智能指针(shared_ptr 可能在这种情况下,但最好的指导是测试您获得的实际性能)。

【讨论】:

    【解决方案2】:

    对于大多数 C++ 编译器,无论您对它们做什么,小型 get/set 方法都会被内联。如果你在头文件中定义了实现,那么它们肯定会被内联!

    唯一的例外是,如果它们被构建到共享库中(这很难内联到调用者的代码中),但即便如此,您也可以在现代编译器中使用链接时内联进行编译,然后也会在那里内联!

    即使是复杂类型,也有一个叫做“return value optimisation”(或 RVO)的东西,您的返回类型被编译为使用与调用者使用的类型相同的存储空间 - 即整个副本都被优化掉了。

    诀窍是让编译器通过保持代码简单来完成它的工作。可以测试您的代码的性能,但我认为您无论如何都会在其他方面调整性能。

    附言。 getter 和 setter ......他们是a bad design。不要鼓励他们。

    【讨论】:

    • getter 和 setter 是糟糕的设计?在 C++ 中还是在一般情况下?并不是说我不同意。
    • @Serge:一般来说——但在某些“地方”(例如,JavaBeans)中使用了很长时间,以至于避免糟糕的设计基本上是不可能的。
    • 我认为 getter/setter 通常是糟糕的设计,因为它们暴露了类的细节而不是创建适当的抽象。 getter/setter 在某些语言中被广泛使用的事实并不能证明不是这样,只能说明这些语言。
    • 很多人说 getter 和 setter 是糟糕的设计,而其他人则说不是。直到知道,我已经看到很多带有 getter 和 setter 的代码,它们易于准备和精心设计。那些告诉我 getter & setter 很糟糕的人实际上从来没有给我其他解决方案或解决方法。_.
    • 解决方案是具有适当的抽象级别,例如MoveTo(2,3) 而不是 SetX(2); SetY(3)
    【解决方案3】:

    [我从我之前在Simple C++ getter/setters 的回答中完全复制了第一段] 通常使用访问器/突变器是一种设计气味,即您的类公共接口不完整。通常来说,您需要一个有用的公共接口来提供有意义的功能,而不是简单地获取/设置(这比我们在 C 中使用结构和函数时要好一两个步骤)。每次你想写一个 mutator,而且很多时候你想先写一个访问器时,只要退后一步,问自己“我真的需要这个吗?”。

    现在,如果您真的想要特定属性的 getter/setter:前两个版本中的任何一个都是正确的,尽管第二个版本在某些情况下可能表现更好。 getter 应该是 const (const string& GetTest() const),以便可以在 const 对象上调用它们。

    带有指针的第三个版本非常危险。虽然前两个具有非常明确的所有权语义,但第三个没有。您显示的简短 sn-p 没有清理,这意味着 set 函数的调用者必须在您的课程期间保持字符串活动(没有所有权转移)。完全避免这种结构。

    最后,如果您只是学习 C++,请考虑 C++ 书籍列表中的一本或多本书籍The Definitive C++ Book Guide and List

    编辑:如果您想提高代码的性能,最好的方法是进行优化编译,然后分析并查看热点在哪里。

    【讨论】:

    • 我不是在学习 c++,现在我正在尝试提高我的代码的性能
    【解决方案4】:

    如果你仅限于 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) 时,参数也是 move 构造的,因此在这种情况下避免所有副本,并且与您的第三个解决方案一样高效,没有问题。

    请注意,对于大尺寸类型(例如具有许多成员的结构),浅拷贝已经很昂贵,因此即使在 C++11 中,您的第二个版本仍然是此类类型的最佳选择。对于此类类型,额外的右值版本不会提高性能,除非该类额外管理分配的资源。

    当然,大多数时候正确的解决方案是完全没有 getter/setter。

    【讨论】:

    • 这不能很好地与 SSO 配合使用,是吗?或者优化器是否足够聪明,可以处理 2 个 SSO 副本?
    • 我实际上并没有想到小字符串优化(我认为这就是 SSO 的意思,对吧?)。但是我希望它只用于 small 字符串,其中复制仍然很便宜(实际上,我想知道它在 C++11 中是否仍然值得)。您是否有任何关于 C++11 实现中 SSO 优化字符串的最大长度的真实数据?
    • 22 个字符,可能因实现而异。 10 个 32 位字符。 stackoverflow.com/questions/21694302/…£ 这意味着它会影响大多数情况下,字符串分配得足够频繁,以至于优化字符串设置器变得有价值。
    • 但是,这只是三个词,而参考一个词。复制两个额外的单词真的比间接更昂贵(这在引用的情况下是不可避免的,除非在函数被内联的情况下,我希望额外的副本无论如何都会被优化掉)?事实上,如果调用约定经常将这三个词放入寄存器中,我不会感到惊讶。
    【解决方案5】:

    您在问题中列出的这 3 个版本做了 3 件完全不同的事情,这就是它们以不同速度运行的原因。在 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);
    }
    

    【讨论】:

    • 我的第二个版本的主要问题是 setter 中的复制部分。如果它是我必须使用的参数,我只会更改它的副本。如果我使用构造函数,同样的问题是: MyClass(Obj& obj){ this->obj = obj); MyClass::Method(){obj.doSometh(...);}
    猜你喜欢
    • 2014-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-20
    • 2011-05-25
    • 2017-02-17
    • 1970-01-01
    • 2016-07-20
    相关资源
    最近更新 更多