3

Recently i did some performance tests using getter and setter methods. I'm still not sure which is the best method to use them in c++. (there is no difference using small types like int etc.)

string i;

string GetTest()
{
    return i;
}
void SetTest(string i)
{
    this->i = i; //copy
}

This would be the way, how I would use it in java/C#. No reference/pointer and very slow in c++. For strings, vectors and other "big" types this is very slow and bad if it gets called frequently

string i;
const string& GetTest()
{
    return i;
}
void SetTest(const string& i)
{
    this->i = i; //copy
}

At the moment I'm using this version above, it's much faster than using no references for the parameter and return value and with const I make sure, it won't be changed.

string* i;

string& GetTest()
{
    return *i;
}
void SetTest(string& i)
{
    this->i = &i;
}

This version, using a pointer is faster than just using references, but I think it's harder to read, and if you want to work with the value inside the class, you have to work with the pointer.

Beside using references / pointers it might also improve the performance a little bit by inlining frequently called getter & setters, since they are small functions.

inline GetTest(){....}
inline SetTest(){....}

But as already mentioned, I'm still not sure which would be the best way in c++ to use them, or if there are other ways. It would be good to eliminate these ambiguities.

Edit:

Coming back to this after nearly 4 years, here is my advice I would give now for modern C++: (more helpful than any text)

https://www.youtube.com/watch?v=xnqTKD8uD64 51:00

I recommend to watch the full video, Herb Sutter such an amazing talker, in general follow CppCon.

jeromintus
  • 367
  • 1
  • 5
  • 14
  • 11
    The version with `this->i = &i;` is very wrong. You keep the pointer to an object of which you do not know anything. I would suggest forget about those optimisations and first learn C++ better. – Wojtek Surowka Sep 09 '14 at 14:58
  • Do performance testing! Don't do premature optimizations! – OmnipotentEntity Sep 09 '14 at 14:58
  • 12
    If you want to use C++ well (at all) you need to move away from Java/C# idioms like using getters and setters everywhere. In C++ we do things a little differently: we work at understanding the real problem and designing the code to solve it well--and that virtually *never* involves using a getter or a setter. – Jerry Coffin Sep 09 '14 at 14:59
  • Consider using `SetTest(string&& i)` instead (only valid from C++11). This will allow a caller to move an existing string that they don't intend to use anymore, which saves you from making an unnecessary copy when the original is going away anyway. Also, consider having just one method returning `string &` if you don't have to do any validation; this will allow the caller to both obtain the string and assign to it in the most optimal way without any work on your part. – cdhowie Sep 09 '14 at 14:59
  • Correctness > speed. Also go, get a profiler/do testing. – Xarn Sep 09 '14 at 15:00
  • 2
    It is common practice to define such trivial one-liner functions in the header file with the class declaration. This will make them implicitly `inline`. – 5gon12eder Sep 09 '14 at 15:03
  • @Xarn not true! Sometimes what people really need are fast programs that output garbage. – R. Martinho Fernandes Sep 09 '14 at 15:03
  • 1
    @R.MartinhoFernandes Correct* fast programs that output garbage. – DSquare Sep 09 '14 at 15:04
  • Getters/setters are not object oriented, they are procedural. They take decisions about the data/state of an object outside the methods of that object. – Rob K Sep 09 '14 at 16:45

5 Answers5

3

[I copied this first paragraph exactly from my previous answer at Simple C++ getter/setters ] Generally using accessors/mutators at all is a design smell that your class public interface is incomplete. Typically speaking you want a useful public interface that provides meaningful functionality rather than simply get/set (which is just one or two steps better than we were in C with structs and functions). Every time you want to write a mutator, and many times you want to write an accessor first just take a step back and ask yourself "do I really need this?".

Now if you really want to have getters/setters for specific attributes: Either of the first two versions are correct although the second might perform better in some circumstances. The getters should be const (const string& GetTest() const) so they can be called on const objects.

Your third version with the pointer is pretty dangerous. While the first two have very clear ownership semantics, the third one does not. The short snippet you've shown, with no cleanup, implies that the caller to the set function must keep the string alive for the duration of your class (no ownership transfer). Just avoid that construction completely.

Finally if you're just learning C++ consider one or more of the books at the C++ book list The Definitive C++ Book Guide and List

EDIT: If you're trying to improve the performance of your code the best approach is to compiler with optimization and then profile and see where the hot spots are.

Community
  • 1
  • 1
Mark B
  • 95,107
  • 10
  • 109
  • 188
2

If you are restricted to C++98/C++03, your second code is indeed the optimal solution. The third code is error prone and unidiomatic, and if fixing that by using pointers or, better, smart pointers, as arguments it's still not ideal (and you'll also have to deal with memory management for the string).

If you can use C++11 or C++14, the optimal setter implementation for types that indirectly hold their data and implement move semantics (like std::string) is

void SetTest(std::string a_i) // by value!
{
  i = std::move(a_i); // move assignment
}

The reason why this is optimal is that for lvalue arguments, it will cost about as much as the reference implementation (because the std::move prevents copying of the actual data), but for rvalue arguments (for example the return value of another function, or when explicitly using std::move) the argument is move constructed as well, thus in this case avoiding all copies and being as efficient as your third solution, without its problems.

Note that for types of large size (like structs with many members), already a shallow copy will be costly, and therefore your second version is still optimal for such types even in C++11. For such types an additional rvalue version will not improve performance unless the class additionally manages allocated resources.

Of course, most of the time the correct solution will be to have no getters/setters at all.

celtschk
  • 19,311
  • 3
  • 39
  • 64
  • That doesn't play nicely with SSO, does it? Or is the optimizer smart enough to take care of the 2 SSO copies? – Peter Sep 09 '14 at 16:21
  • I actually didn't think of small string optimization (I think that's what you mean with SSO, right?). However I'd expect it only to be used for *small* strings, where copying is still cheap (indeed, I wonder whether it is still worthwhile in C++11 at all). Do you have any real world data about the maximum length of SSO optimized strings in C++11 implementations? – celtschk Sep 09 '14 at 16:31
  • 22 characters, with possible variations by implementation. 10 characters in 32 bit. http://stackoverflow.com/questions/21694302/what-are-the-mechanics-of-short-string-optimization-in-libc£ That means it affects most cases where strings are assigned sufficiently often that optimizing a string setter becomes worthwile. – Peter Sep 09 '14 at 16:33
  • But then, it's just three words, vs. one word for the reference. Is copying two extra words really more expensive than an indirection (which is inevitable in the case of references except in the case where the function is inlined, where I'd expect the extra copy to be optimized away anyway)? Indeed, I'd not be surprised if quite often the calling conventions would put the three words into registers anyway. – celtschk Sep 09 '14 at 17:00
2

These 3 versions you listed in your question do 3 completely different things, that's why they run at different speeds. The "right" way to do string getters and setters in C++ is this:

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);
}

Despite what some other contributors say, getters and setters are not necessarily bad class design. They are often preferable to direct access to member variables. However, excessive use of Getters/Setters (or direct access to member variables) is a code smell.

Your first version is solid and correct (except that the Getter isn't const, but it requires the compiler to unnecessarily copy and destroy the string in various situations:

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

The third version you suggested should be avoided at all cost because it's a brittle design that is guaranteed to lead to unexpected bugs:

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"

The "right" way to do the third version if you really really really need java/c#-like behavior is the following :

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);
}
Peter
  • 5,608
  • 1
  • 24
  • 43
  • the main problem for my second version is the copy part in the setter. if it would be a a parameter which i have to work with, i would only change it's copy. the same problem would be if i use a constructor: MyClass(Obj& obj){ this->obj = obj); MyClass::Method(){obj.doSometh(...);} – jeromintus Sep 10 '14 at 10:42
0

If you store pointers to data in your getter and setter you are going to have major issues with ownership and object lifetimes. Jave and C# have their own ways of dealing with this. In C++ you'll need to use some variety of smart pointer (shared_ptr probably in this situation, but the best guide to this will be to test the actual performance you get).

Tom Tanner
  • 9,244
  • 3
  • 33
  • 61
0

With most C++ compilers, the small get/set methods will be inlined no matter what you do to them. If you define the implementation in the header file, then they will definitely be inlined!

The only exception is if they are built into a shared library (which is tricky to inline into a caller's code) but even then, you can compile with link-time inlining in modern compilers and then will be inlined there too!

Even with complex types, there's a thing called "return value optimisation" (or RVO) where your return types are compiled to use the same storage as the type used by the caller - ie the entire copy is optimised away.

The trick is to allow the compiler to do its thing by keeping your code simple. Its possible to test your code for performance, but then I think you'll be tweaking performance in other areas anyway.

PS. getters and setters... they're a bad design. Don't encourage them.

gbjbaanb
  • 51,617
  • 12
  • 104
  • 148
  • Getters and setters are bad design? In C++ or in general? Not that I'm disagreeing. – Serge Sep 09 '14 at 15:05
  • 1
    @Serge: In general--but in some "places" (e.g., JavaBeans) used so heavily for so long that avoiding bad design is essentially impossible. – Jerry Coffin Sep 09 '14 at 15:07
  • 2
    I think that getters/setters are bad design in general because they expose details of the class instead of creating appropriate abstraction. The fact that getters/setters are used widely in some languages does not prove otherwise, only says something about those languages. – Wojtek Surowka Sep 09 '14 at 15:08
  • 1
    A lot of people say getter & setter are bad design, and other say they aren't. Until know, I've seen a lot of code with getter & setters which were easy to ready & well designed. People who told me getter & setters are bad actually never gave me another solution or workaround for them ._. – jeromintus Sep 09 '14 at 15:11
  • 1
    The solution is to have appropriate level of abstraction, e.g. `MoveTo(2,3)` instead of `SetX(2); SetY(3)`. – Wojtek Surowka Sep 09 '14 at 15:19
  • @Sleicreider: Sounds like (perhaps) a subject for a question, though possibly more suitable for [CodeReview](http://codereview.stackexchange.com/) than here. Post working code and see if anybody can suggest improvements that would eliminate the getters/setters. – Jerry Coffin Sep 09 '14 at 15:20
  • @Sleicreider from a design PoV, a getter/setter is no different from a public variable! (sure, you can put checking code in there, but nobody does tha - even the 'other languages' have syntactic sugar for creating them (ie `public property x { get; set; }`). A good OO design thinks about the required class interface, not the variables that make up its implementation. – gbjbaanb Sep 09 '14 at 15:23
  • @WojtekSurowka well this is actually just a "combined setter" – jeromintus Sep 09 '14 at 15:25
  • Apart from checking code, getters/setters have the advantage over public members that at a later time you at least can replace the variable by something else without breaking the code; say you find out your `std::string` member turns out to be a bottleneck, but there's a super-efficient string class you can use, then you can replace the private member to that and rewrite the getter/setter to convert between std::string and that super-efficient string class; with a public member you're stuck with std::string no matter what. However, that only works if you don't return a reference to the member. – celtschk Sep 09 '14 at 15:41
  • @celtschk erm.. what type of variable do you return in your getter? Because, if you did change your string type internally, you'd then have to convert it to the std::string you used to return, which is probably worse. Don't focus on the types, write access methods that interact with the class in a appropriately abstract way. – gbjbaanb Sep 09 '14 at 16:44
  • @gbjbaanb: I don't dispute that not having getters/setters *at all* is the best. I just say that getters/setters are still much better than public variables. And yes, the getter would have to convert to `std::string`. But then, if the class contains some string, chances are extremely high that the interface will use `std::string` somewhere even without getters/setters, so you'll not be able to avoid such conversions even with a better interface. The assumption above is, of course, that the bottleneck is in the internal use of the string inside the class. – celtschk Sep 09 '14 at 16:50