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\n";
}

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

StringBad::~StringBad()
{
    cout << "\"" << str <<"\" object deleted, ";
    --num_strings;
    cout << num_strings << " left\n";
    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.\n";
        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:\n";
        StringBad sailor = sports;
        cout << "sailor: " << sailor << endl;
        cout << "Assign one object to another:\n";
        StringBad knot;
        knot = headline1;
        cout << "knot: " << knot << endl;
        cout << "Exiting the block.\n";
    }
    cout << "End of main()\n";

    return 0;
}

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

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

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\n";
}

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] = ‘\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() != ‘\n‘)
        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?\n>> ";
    cin >> name;
    cout << name << ", please enter up to " << ArSize << " short sayings <empty line to quit>:\n";
    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() != ‘\n‘)
            continue;
        if (!cin || temp[0] == ‘\0‘)
            break;
        else
            sayings[i] = temp;
    }
    int total = i;

    if (total > 0)
    {
        cout << "Here are your sayings:\n";
        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:\n" << sayings[shortest] << endl;
        cout << "First alphabetically:\n" << sayings[first] << endl;
        cout << "This program used " << String::HowMany() << " String objects. Bye.\n";
    }
    else
        cout << "No input!\n";
    return 0;
}


Cpp Chapter 12: Classes and Dynamic Memory Allocation Part1

原文地址:https://www.cnblogs.com/fsbblogs/p/9798343.html

时间: 2024-10-07 11:34:59

Cpp Chapter 12: Classes and Dynamic Memory Allocation Part1的相关文章

Cpp Chapter 12: Classes and Dynamic Memory Allocation Part2

12.3 Things to remember when using new in constructors ) If you use new in constructors, use delete in destructor. Their use should be compatible, pair new with delete and new [] with delete [] ) Multiple constructors should share the same way of new

[Paper翻译]Scalable Lock-Free Dynamic Memory Allocation

原文: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.3870&rep=rep1&type=pdf Abstract 动态内存分配器(malloc/free)在多线程环境下依靠互斥锁来保护共享数据的一致性.使用锁在性能,可用性,健壮性,程序灵活性方面有很多缺点.Lock-free的内存分配器能消除线程延迟或被杀死以及CPU的调度策略对程序的性能影响.这篇paper呈上了一个完整的无锁内存分配器.它的实现只使用被广泛支

C++ storage allocation + Dynamic memory allocation + setting limits + initializer list (1)

1. 对象的空间在括号开始就已经分配,但是构造在定义对象的时候才会实现,若跳过(譬如goto),到括号结束析构会发生错误,编译会通不过. 2.初始化 1 struct X { int i ; float f; char c;}; 2 3 - X x1 = { 1,2.2,'c'}; 4 X x2[3] = { {1,1.1,'a'},{2,2.2,'b'} }; 5 6 7 struct Y {float f; int i; Y(int a);} ; 8 9 Y y1[] = {Y(1),Y(2

A Reusable Aspect for Memory Allocation Checking

The checking logic would be refactored into an aspect file, as follows: after(void * s) : (call($ malloc(...)) || call($ calloc(...)) || call($ realloc(...))) && result(s) { char * result = (char *)(s); if (result == NULL) { /* routine to handle t

Cpp Chapter 9: Memory Models and Namespaces Part2

9.2.4 Static duration, external linkage ) External variables External variables are defined outside, thus external to any function.It is also termed global variables, which could be accessed in any function that follow's the external variable's defin

Cpp Chapter 10: Objects and Classes Part2

10.2.4 Using classes Following exapmle uses the class definition and implementation written in previous files: // usestok0.cpp -- the client program // compiler with stock00.cpp #include <iostream> #include "stock00.h" int main() { Stock s

Cpp Chapter 11: Working with Classes Part2

11.5.3 An implementation comment ) The separation of interface from implementation is one of the goals of OOP. ) Example of the implementation of the class Vector in a rand walk simulation: // randwalk.cpp -- using the Vector class // compile with th

Cpp Chapter 13: Class Inheritance Part1

class inheritance lets you derive new classes from old ones, inheriting its properties of the old class, called the base class With inheritance, you can: 1 add functionality to existing classes 2 add the data a class represents 3 modify how a class m

c++ Dynamic Memory

1. make_shared<T>(args): return a shared_ptr dynamically allocated object of type T. Use args to initialize the object. shared_ptr<T> p(q): p is a copy of shared_ptr q. Increase the count in q. The pointer in q must be convertable to T. p = q: