Cpp Chapter 12: Classes and Dynamic Memory Allocation Part1

Posted fsbblogs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cpp Chapter 12: Classes and Dynamic Memory Allocation Part1相关的知识,希望对你有一定的参考价值。

12.1 Dynamic memory and classes

12.1.1 A review example and static class members

Now try implement a String class(a flawed one):

// strngbad.h -- flawed string class definition
#include <iostream>
#ifndef STRNGBAD_H_INCLUDED
#define STRNGBAD_H_INCLUDED
class StringBad
{
private:
    char * str;
    int len;
    static int num_strings;
public:
    StringBad(const char * s);
    StringBad();
    ~StringBad();
    friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
};

#endif // STRNGBAD_H_INCLUDED

num_strings is a static class member, which has a special property: A program only creates one copy of the static class variable, regardless of the number of objects created. Which is to say, every object belonging to the class share a same static class member.

// strngbad.cpp -- StringBad class methods
#include <cstring>
#Include "strngbad.h"
using std::cout;

int StringBad::num_strings = 0; // static class member initialized outside

StringBad::StringBad(const char * s)
{
    len = std::strlen(s);
    str = new char[len+1];
    std::strcpy(str, s);
    num_strings++;
    cout << num_strings << ": "" << str << "" object created
";
}

StringBad::StringBad()
{
    len = 4;
    str = new char[4];
    std::strcpy(str, "C++");
    num_strings++;
    cout << num_strings << ": "" << str << "" default object created
";
}

StringBad::~StringBad()
{
    cout << """ << str <<"" object deleted, ";
    --num_strings;
    cout << num_strings << " left
";
    delete [] str; // required!
}

std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
    os << st.str;
    return os;
}

Noteworthy:
1 A static data member is declared in the class declaration but initialized in the file containing class methods, which uses a scope operator to indicate the class it belongs to:

int StringBad::num_strings = 0;

This is because the declaration of the class doesn‘t allocate memory, and static class members are stored independently. There are also exceptions: when it is a const of integral or enumeration type, then it could be initialized inside the class declaration.
2 Code of the destructor:

StringBad::~StringBad()
{
    --num_strings;
    delete [] str; // !
}

The "--num_strings" updates the current number of objects. When you use new in a constructor to allocate memory, use delete in the destructor to free that memory, if you used new [] then use delete [] here. In both default and non-default constructor, it uses "str = new char[...]" to allocate memory for pointer str, so you shoule use "delete [] str" to free that memory

// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
#include "strngbad.h"
using std::cout;

void callme1(StringBad &);
void callme2(StringBad);

int main()
{
    using std::endl;
    {
        cout << "Starting an inner block.
";
        StringBad headline1("Celery Stalks at Midnight");
        StringBad headline2("Lettuce Prey");
        StringBad sports("Spinach Leaves Bowl for Dollars");
        cout << "headline1: " << headline1 << endl;
        cout << "headline2: " << headline2 << endl;
        cout << "sports: " << sports << endl;
        callme1(headline1);
        cout << "headline1: " << headline1 << endl;
        callme2(headline2);
        cout << "headline2: " << headline2 << endl;
        cout << "Initialize one object to another:
";
        StringBad sailor = sports;
        cout << "sailor: " << sailor << endl;
        cout << "Assign one object to another:
";
        StringBad knot;
        knot = headline1;
        cout << "knot: " << knot << endl;
        cout << "Exiting the block.
";
    }
    cout << "End of main()
";

    return 0;
}

void callme1(StringBad & rsb)
{
    cout << "String passed by reference:
";
    cout << "    "" << rsb << ""
";
}

void callme2(StringBad sb)
{
    cout << "String passed by value:
";
    cout << "    "" << sb << ""
";
}

The program will eventually end in disastrous results, simply due to the member functions generated by the compiler automatically. Here comes the topic:


12.1.2 Special member functions

) In particular, C++ provides 5 special member functions automatically if you don‘t define one:
1 A default constructor if you define no constructors
2 A default destructor if you don‘t define one
3 A copy constructor if you don‘t define one
4 An assignment operator if you don‘t define one
5 An address operator if you don‘t define one
We will focus on the highlighted member functions in the following:

) Default constructors
If you fail to provide any constructor at all, C++ provides you with a default constructor:

Klunk::Klunk() {} // implicit default constructor

You could also define a default constructor by yourself, which takes no arguments:

Klunk::Klunk() // explicit default constructor
{
    klunk_ct = 0;
    ...
}

A constructor with arguments could still be default constructor if all arguments have a default value, however, you could only have one default constructor

) Copy constructors
A copy constructor is used to copy an object to a newly created object, its prototype:

Class_name(const Class_name &);

A copy constructor is invoked when a new object is created and initialized to an existing object of the same kind:
1 explicitly initialize a new object to an existing object

StringBad metoo = motto;
StringBad metoo(motto);
StringBad metoo = StringBad(motto); // they all call StringBad(const StringBad &)

2 when the program generates copies of an object(pass by value, return an object, etc.)
3 generates temporary objects(like the intermediate result of a calculation)
After calling, the default copy constructor performs a member-by-member copy of nonstatic members(memberwise copying), each member is copied by value.
Static members are not affected by default copy constructors


12.1.3 Back to strngbad: where the copy constructor goes wrong

) One of the problems in the above program: the default constructor created 2 objects(one for pass by value and one for assignment) without increment of the counter variable num_strings.The solution is to provide an explicit copy constructor that updates the value:

StringBad::StringBad(const String & s)
{
    num_strings++;
    ...
}

) Another problem, responsible for the scrambled output text,occurs here:

StringBad sailor = sports;

Recall that this uses the default copy constructor simply do memberwise copy, so it has

sailor.str = sports.str;

Note that str member is a pointer, so it copies the address to sailor‘s member str. When it comes to destructor, it will free the same memory twice, which leads to undefined results when the memory gets freed for the second time.

) Fixing the problem
Make a deep copy:the copy constructor should duplicate the string and assign and address of str to the new string.

StringBad::StringBad(const StringBad & st)
{
    num_strings++; // update
    len = st.len;
    str = new char[len+1];
    std::strcpy(str,st.str);
    cout << num_strings << ": " << str << "" object created
";
}

You should use the thought of deep copy when classes contain members which are allocated by new or pointers


12.1.4 More Stringbad problems: assignment operators

) C++ allows class assignment by overloading the "=" operator:

Class_name & Class_name::operator=(const Class_name &);

) An assignment operator is called when you assign one object to another existing object:

StringBad headline1("aaa");
StringBad knot;
knot = headline1; // assignment operator

) Default assignment operator performs memberwise copy, leaving static members unaffected. If a class contains a pointer member, this eventually goes to double freeing the same chunk of memory
) Solution code(providing an assignment operator):

StringBad & StringBad::operator=(const StringBad & st)
{
    if (this == &st) /1
        return *this; /2
    delete [] str; /3
    len = st.len; /4
    str = new char[len+1]; /5
    std::strcpy(str,st.str) /6
    return *this; /7
}

Noteworthy:
1 Line 1-2 checks whether the program is trying to assign the same object to the original, in order to prevent the delete of previous data, the program terminates under the condition of self=self
2 Line 3-6 free original memory used by the object and allocate new memory to the object
3 Line 7 return the result of the invoking object to suit this situation:

S0 = S1 = S2;

12.2 The new, improved String class

) C++11 adds keyword nullptr for null pointer
) Access characters by using bracket notation:

char & String::operator[](int i)
{
    return str[i];
}

) Static member functions
In order to make a member function static, simply add the keyword static to the function. Differences:
1 static member functions don‘t get invoked by objects(which means they also don‘t have this pointer). They could be called using scope-resolution operator:

static int HowMany() {return num_strings;}
...
int count = String::HowMany(); // call static member function

2 static member function could only access static data member, since no object is bind to it
Before code, let‘s emphasize:
copy: copy an object to a newly created object
assign: assign an object to an existing object
Example:

// string1.h -- fixed and augmented string class definition

#ifndef STRING1_H_INCLUDED
#define STRING1_H_INCLUDED
#include <iostream>
using std::ostream;
using std::istream;

class String
{
private:
    char * str;
    int len;
    static int num_strings;
    static const int CINLIM = 80;
public:
    String(const char * s);
    String();
    String(const String &); // copy constructor
    ~String();
    int length () const {return len;}
    String & operator=(const String &);
    String & operator=(const char *);
    char & operator[](int i);
    const char & operator[](int i) const;
    friend bool operator<(const String &st, const String &st2);
    friend bool operator>(const String &st, const String &st2);
    friend bool operator==(const String &st, const String &st2);
    friend ostream & operator<<(ostream & os, const String & st);
    friend istream & operator>>(istream & is, String & st);
    static int HowMany();
};

#endif // STRING1_H_INCLUDED
// string1.cpp -- String class methods
#include <cstring>
#include "string1.h"
using std::cin;
using std::cout;

int String::num_strings = 0;

int String::HowMany()
{
    return num_strings;
}

String::String(const char * s)
{
    len = std::strlen(s);
    str = new char[len+1];
    std::strcpy(str,s);
    num_strings++;
}

String::String()
{
    len = 4;
    str = new char[1];
    str[0] = ‘‘;
    num_strings++;
}

String::String(const String & st)
{
    num_strings++;
    len = st.len;
    str = new char[len+1];
    std::strcpy(str,st.str);
}

String::~String()
{
    delete [] str;
    num_strings--;
}

String & String::operator=(const String & st)
{
    if (this == &st)
        return *this;
    delete [] str;
    len = st.len;
    str = new char[len+1];
    strcpy(str, st.str);
    return *this;
}

String & String::operator=(const char * s)
{
    delete [] str;
    len = std::strlen(s);
    str = new char[len+1];
    strcpy(str, s);
    return *this;
}

char & String::operator[](int i)
{
    return str[i];
}

const char & String::operator[](int i) const
{
    return str[i];
}

bool operator<(const String & st1, const String & st2)
{
    return std::strcmp(st1.str,st2.str)<0;
}

bool operator>(const String & st1, const String & st2)
{
    return st2 < st1;
}

bool operator==(const String & st1, const String & st2)
{
    return std::strcmp(st1.str,st2.str) == 0;
}

ostream & operator<<(ostream & os, const String & st)
{
    os << st.str;
    return os;
}

istream & operator>>(istream & is, String & st)
{
    char temp[String::CINLIM];
    is.get(temp, String::CINLIM);
    if (is)
        st = temp;
    while (is && is.get() != ‘
‘)
        continue;
    return is;
}
// sayings1.cpp -- using expanded String class
// compile with string1.cpp
#include <iostream>
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
    using std::cout;
    using std::cin;
    using std::endl;
    String name;
    cout << "Hi, what‘s your name?
>> ";
    cin >> name;
    cout << name << ", please enter up to " << ArSize << " short sayings <empty line to quit>:
";
    String sayings[ArSize];
    char temp[MaxLen];
    int i;
    for (i = 0; i < ArSize; i++)
    {
        cout << i+1 << ": ";
        cin.get(temp, MaxLen);
        while (cin && cin.get() != ‘
‘)
            continue;
        if (!cin || temp[0] == ‘‘)
            break;
        else
            sayings[i] = temp;
    }
    int total = i;

    if (total > 0)
    {
        cout << "Here are your sayings:
";
        for (int i = 0; i < total; i++)
            cout << sayings[i][0] << ": " << sayings[i] << endl;

        int shortest = 0;
        int first = 0;
        for (int i = 1; i < total; i++)
        {
            if (sayings[i].length() < sayings[shortest].length())
                shortest = i;
            if (sayings[i] < sayings[first])
                first = i;
        }
        cout << "Shortest saying:
" << sayings[shortest] << endl;
        cout << "First alphabetically:
" << sayings[first] << endl;
        cout << "This program used " << String::HowMany() << " String objects. Bye.
";
    }
    else
        cout << "No input!
";
    return 0;
}
















以上是关于Cpp Chapter 12: Classes and Dynamic Memory Allocation Part1的主要内容,如果未能解决你的问题,请参考以下文章

Cpp Chapter 11: Working with Classes Part2

Cpp Chapter 10: Objects and Classes Part2

Cpp Chapter 10: Objects and Classes Part3

Cpp Chapter 10: Objects and Classes Part1

Cpp Chapter 13: Class Inheritance Part1

Cpp Chapter 9: Memory Models and Namespaces Part2