References c++ question

Started by
8 comments, last by Zahlman 18 years, 4 months ago
I'm doing exercise 1 in chapter 6 from the book "beginning c++ game programming". The exercise is to improve this code and make it more efficient with references.
[SOURCE]
#include <iostream>
#include <string>

using namespace std;

string askText(string prompt);
int askNumber(string prompt);
void tellStory(string name, string noun, int number, string bodyPart,
               string verb);

int main()
{
    string name, noun, bodyPart, verb;
    int number;

    cout << "Welcome to Mad Lib.\n\n";
    cout << "Answer the following questions to help create a new story.\n";

    name      =       askText("Please enter a name: ");
    noun      =       askText("Please enter a plural noun: ");
    number    =       askNumber("Please enter a number: ");
    bodyPart  =       askText("Please enter a body part: ");
    verb      =       askText("Please enter a verb: ");

    tellStory (name, noun, number, bodyPart, verb);

    while (!!cin) cin.get();
    return 0;
}

string askText(string prompt)
{
    string text;
    cout << prompt;
    cin >> text;
    return text;
}

int askNumber (string prompt)
{
    int num;
    cout << prompt;
    cin >> num;
    return num;
}

void tellStory(string name, string noun, int number, string bodyPart,
                string verb)
{
    cout << "\nHere's your story:\n";
    cout << "The famous explorer ";
    cout << name;
    cout << " had nearly given up a life-long quest to find\n";
    cout << "The Lost City of ";
    cout << noun;
    cout << " when one day, the ";
    cout << noun;
    cout << " found the explorer.\n";
    cout << "Surrounded by ";
    cout << number;
    cout << " " << noun;
    cout << ", a tear came to ";
    cout << name << "'s ";
    cout << bodyPart << ".\n";
    cout << "After all this time, the quest was finally over. ";
    cout << "And then, the ";
    cout << noun << "\n";
    cout << "promptly devoured ";
    cout << name << ". ";
    cout << "The moral of the story? Be careful what you ";
    cout << verb;
    cout << " for.";
}
[/SOURCE]
I know how it works, I feel pretty familiar with references too, but I just can't find out how they mean and how I should do it. Can anyone help me with this, how would you solve it, thanks.
Advertisement
Take a look at the function parameters. All of them are being passed by value, which means that each time you call a function, a copy of each parameter is made and then sent to the function. For intrinsics like int this doesn't matter, but for complex objects like strings it can make a lot of overhead.

Look carefully at what each function does with its parameters, and determine what it needs:

- Value copy
- Reference
- Reference to constant
- Pointer
- Pointer to constant object


Figure out the best option for each parameter, and make the appropriate changes.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Do you think it's enough to pass the prompt string as a reference alone?
Quote:Original post by password
Do you think it's enough to pass the prompt string as a reference alone?


If you do that you will have to explicitly instantiate those strings when calling those functions.
Edited the code like this:

[SOURCE]string askText(const string& prompt);int askNumber(const string& prompt);void tellStory(string name, string noun, int number, string bodyPart,               string verb);[/SOURCE]


[SOURCE]string askText(const string& prompt){    string text;    cout << prompt;    cin >> text;    return text;}int askNumber (const string& prompt){    int num;    cout << prompt;    cin >> num;    return num;}[/SOURCE]


It worked that way, but it's not edited that much, i'm just not sure if that's what they're asking for.
Any particular reason you didn't change tellStory()?

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Actually, I totally forgot that function, thanks for help.
I hope i can help, ive changed the code to what ive would have done(doing your assignment ;) );

/*	story.cpp*/#include <iostream>#include <string>using namespace std;void askText(string prompt, string & answer); //passing the value that will hold the answer by reference. no need for a return valuevoid askNumber(string prompt, int & answer);	//same as abovevoid tellStory(string name, string noun, int number, string bodyPart,               string verb);int main(){    string name, noun, bodyPart, verb;    int number;    cout << "Welcome to Mad Lib.\n\n";    cout << "Answer the following questions to help create a new story.\n";    askText("Please enter a name: ", name); 		//instead of the fuction returning the answer, it will write directly to its place in memory    askText("Please enter a plural noun: ", noun);	//same    askNumber("Please enter a number: ", number);	//same    askText("Please enter a body part: ", bodyPart);//same    askText("Please enter a verb: ", verb);			//same    tellStory (name, noun, number, bodyPart, verb);    while (!!cin) cin.get();    return 0;}void askText(string prompt, string & answer) //get a reference to where the value is stored{    //string text; - not needed    cout << prompt;    cin >> answer;	//..then write the answer there    //return text; - not needed, }void askNumber (string prompt, int & answer) //same as askText(){    //int num;    cout << prompt;    cin >> answer;    //return num;}void tellStory(string name, string noun, int number, string bodyPart,                string verb){    cout << "\nHere's your story:\n";    cout << "The famous explorer ";    cout << name;    cout << " had nearly given up a life-long quest to find\n";    cout << "The Lost City of ";    cout << noun;    cout << " when one day, the ";    cout << noun;    cout << " found the explorer.\n";    cout << "Surrounded by ";    cout << number;    cout << " " << noun;    cout << ", a tear came to ";    cout << name << "'s ";    cout << bodyPart << ".\n";    cout << "After all this time, the quest was finally over. ";    cout << "And then, the ";    cout << noun << "\n";    cout << "promptly devoured ";    cout << name << ". ";    cout << "The moral of the story? Be careful what you ";    cout << verb;    cout << " for.";}


All changes should be commented.

Basically instead of the functions returning a copy of the answer, i pass them a reference to the memory and they write the answer directly, with no need for a returnvalue.
Cake for me, please.
Maybe it would help to describe what happens when you call a function using pass-by-value and pass-by-reference.


Pass-by-value


#include<string>void PassByValue(std::string FunctionString);int main(){  std::string StringOne("hello");  const char* pStringTwo = "hello";  PassByValue(StringOne);  PassByValue(pStringTwo);}



In the first call to PassByValue, you are calling the function with an std::string. Since the PassByValue function takes an std::string as a parameter, the std::string StringOne is used to initialize the std::string FunctionString using the copy constructor.

The strings FunctionString and StringOne are completly different strings, so changing one will not change the other. This means to call the function you have created a copy of the std::string StringOne, this copy is the string FunctionString .

In the second call to PassByValue, you are trying to call the function with a string literal. In C++, string literals have type const char[]. So "hello" is a variable of type const char[6]. The function PassByValue takes a parameter of type std::string. This means the const char pointer pStringTwo will need to be converted to an std::string in order to initialize the PassByValue parameter. This can happen in one of two ways in C++, one way makes use of a standard optimization, the other does not.

The first possible outcome is that the const char pointer pStringTwo will be used to initialize a temporary std::string object, this temporary std::string object will then be used to initialize the std::string FunctionString. In this method, you have created a temporary string, and then made a copy of this temporary string, namely FunctionString.

The second possible outcome makes use of a standard optimization. In this case, the const char pointer pStringTwo is used to directly initialize the std::string FunctionString, skipping out the temporary. It is up to the particular implementation whether or not to make this optimization though.


Pass-by-const-reference

#include<string>void PassByConstReference(const std::string& FunctionString);int main(){  std::string StringOne("hello");  const char* pStringTwo = "hello";  PassByConstReference(StringOne);  PassByConstReference(pStringTwo);}


Here we are using the same code from the Pass-By-value, with the exception that we are using a function which has an std::string const reference as a parameter instead of a string.

In the first call to PassByConstReference, we are passing in the std::string StringOne. Since the function PassByConstReference has a const reference parameter, no temporary objects or copies are created in the process of initializing the const reference parameter. FunctionString is simply an alias for the std::string StringOne.

In the second call to PassByConstReference, we are attempting to pass the const char* into the function again. Like the unoptimized Pass-By-Value function call, we will need to create a temporary std::string object from the const char* in order to initialize the std::string const reference. FunctionString is then simply an alias of the temporary std::string created from the const char*.


Which is more efficient in your code?

string askText(string prompt);string askTextRef(const string& prompt);int main(){askText("hello");askTextRef("hello");}


There isn't really a right and wrong answer as to which is fastest in the case of your askText function, as you are only passing a const char* into the function. Which one is fastest (if either) depends on whether the implementation makes use of the optimization for Pass-by-value described above.

In the call to askText with the const char*, at best you are going to avoid the temporary object, and just create a new instance of an std::string, namely the std::string prompt, using its const char* constuctor. This means one std::string will be created(prompt). At worst you are going to create a temporary object which is used to initialize the std::string prompt. This means two std::strings will be created, the temporary and prompt.

In the call to askTextRef with a const char*, at best and at worst, you are going to create a temporary std::string which will be aliased by the prompt reference. This means you will only ever create one std::string in calling the function.

So the best case is the same for each method, both will create one std::string in the best case when called with a const char*. The askTextRef has the best worst case scenario though, avoiding one extra std::string creation over the Pass-by-value version.

So in short, it depends on the compiler whether either is faster than the other.

It should be clear that the tellStory function is slightly different. tellStory is only being called with std::string objects, and so the const reference version will also be quicker, as no temporaries will be constructed in the process of calling the function.

The most efficient parameter for your askText and askNumber functions, as they stand in your example code, would be a const char*.

[Edited by - Mxz on December 3, 2005 3:08:25 PM]
Some guidelines:

Use references when you can; use pointers when you have to. References give more information to both the compiler (which may be able to do additional optimization as a result, and it can't hurt) and to you (ultimately you spend more time reading your code than writing it).

Store pointers rather than references as data members. Trying to store the references is trickier than it is worth, and I believe not always possible. This is one of those "have to" cases (another common one is when you need to deal with someone else's designed-for-C API). I find it more useful to think of references not as types unto their own but just in terms of calling convention: "passing BY reference" rather than "passing A reference".

Pass primitives (including pointers to objects) by value or non-const reference. Pass objects (class/struct instances) by reference (whether or not const). Use the non-const reference when you require that a change in the parameter is "seen" by the calling function. However, you should normally prefer to return a value instead, if possible.

Use local const values for constants. Use local const references to extract "common subexpressions", if the subexpression evaluates to an object type.

Examples:

int foo() {  const int myLocalConstant = 63;  return myLocalConstant * myLocalConstant;}int bar() {  // old  // return someGlobalObject->x[y].z() + someGlobalObject->x[y].zz();  const Thing& foo = someGlobalObject->x[y];  return foo.z() + foo.zz();  // If it were:  // return someGlobalObject->x[y]->z() + someGlobalObject->x[y]->zz();  // You might instead put:    // const Thing* foo = someGlobalObject->x[y];  // return foo->z() + foo->zz();  // if it were:  // return someGlobalObject->x[y] * someGlobalObject->x[y];  // You might instead put:  // const int foo = someGlobalObject->x[y];  // return foo * foo;}


Const correctness is important, and you should think about it from the beginning. The thing is that it tends to be infectious - once you want to write some bit of code in a const-correct way, it requires you to make a bunch of other stuff const-correct; and a few specific things do need to be const-correct in order to work properly. (For example, copy constructors properly must accept their argument by const reference. Note that they *could not* accept the argument by value, because the function would be invoked in order to copy the thing being passed by value, and would then recurse infinitely.) In particular, watch out for member functions that should be const (like "void foo::bar() const" - it's the this-object that will be made const here).

There is one obvious wart on the C++ design here: References were introduced later on in the game, so "this" is a pointer when it should be a reference (as evidenced by (a) the fact that it makes no sense to change the value of "this" to point to another object; and (b) code full of operator overloads that "return *this;").

This topic is closed to new replies.

Advertisement