نسخه قابل چاپ

بخش ۱۳ - مدیریت حافظه در کلاس‌ها

در این بخش ساختمان داده‌ی پشته در قالب یک کلاس پیاده‌سازی می‌شود. در این پیاده‌سازی برای نگهداری عناصر پشته از آرایه استفاده می‌شود. همان‌گونه که خواهیم دید برای مدیریت درست حافظه در شرایط مختلف نیاز به تعریف مخرب و سازنده‌ی کپی و همچنین سربارگذاری عملگر جایگزینی داریم.

فهرست مثال‌ها

پشته با مخرب

کلاس پشته در حالی که پس‌دادن حافظه‌ی تخصیص‌یافته برای نگهداری عناصر توسط مخرب انجام می‌شود.

#include <iostream>
using namespace std;

#define DEFAULT_SIZE 10

class invalid_operation_ex {};

class stack {
public:
    stack(int size);
    ~stack();
    void push(int x);
    void pop();
    int top() const;
    int elem_count() const { return count; }
private:
    int *elements;
    int size;
    int count;
};

stack::stack(int s = DEFAULT_SIZE) {
    cout << "--constructor called\n";
    size = s;
    elements = new int[size];
    count = 0;
}

stack::~stack() {
    cout << "--destructor called\n";
    delete[] elements;
}

void stack::push(int x) {
    if (count >= size)
        throw invalid_operation_ex();

    elements[count] = x;
    count++;
}

void stack::pop() {
    if (count > 0)
        count--;
    else
        throw invalid_operation_ex();
}

int stack::top() const {
    if (count > 0)
        return elements[count-1];
    else
        throw invalid_operation_ex();
}

int main() {
    stack s;
    s.push(4);
    cout << s.top() << endl;
}
					
مشکل کپی کم‌عمق

این مثال نشان‌دهنده‌ی مشکلی است که هنگام ساختن کپی از اشیایی پیش می‌آید که حافظه‌ی پویا به آنها تخصیص یافته.

#include <iostream>
using namespace std;

#define DEFAULT_SIZE 10

class invalid_operation_ex {};
//تعریف کلاس پشته مانند مثال قبل
class stack {
public:
    stack(int size);
    ~stack();
    void push(int x);
    void pop();
    int top() const;
    int elem_count() const { return count; }
private:
    int *elements;
    int size;
    int count;
};

stack::stack(int s = DEFAULT_SIZE) {
    cout << "--constructor called\n";
    size = s;
    elements = new int[size];
    count = 0;
}

stack::~stack() {
    cout << "--destructor called\n";
    delete[] elements;
}

void stack::push(int x) {
    if (count >= size)
        throw invalid_operation_ex();

    elements[count] = x;
    count++;
}

void stack::pop() {
    if (count > 0)
        count--;
    else
        throw invalid_operation_ex();
}

int stack::top() const {
    if (count > 0)
        return elements[count-1];
    else
        throw invalid_operation_ex();
}

void print_stack(stack s) {
    while (s.elem_count() > 0) {
        cout << s.top() << ' ';
        s.pop();
    }
    cout << endl;
}

int main() {
    stack u;
    u.push(4);
    u.push(5);
    u.push(12);
    print_stack(u);
    u.pop();
    u.pop();
    u.pop();
    // Explain the result of execution of this code
}
					
سازنده‌ی کپی

برای رفع مشکل مثال قبل، باید برای این کلاس سازنده‌ی کپی تعریف کنیم تا هنگام کپی گرفتن از روی یک شیء به طور خودکار تخصیص حافظه را به عهده بگیرد.

#include <iostream>
using namespace std;

#define DEFAULT_SIZE 10

class invalid_operation_ex {};
class stack {
public:
    //تعریف سایر اعضا مانند مثال قبل
    stack(int size);
    stack(const stack&);
    ~stack();
    void push(int x);
    void pop();
    int top() const;
    int elem_count() const { return count; }
private:
    int *elements;
    int size;
    int count;
};

stack::stack(int s = DEFAULT_SIZE) {
    cout << "--constructor called\n";
    size = s;
    elements = new int[size];
    count = 0;
}

stack::stack(const stack& s) {
    cout << "-- copy constructor called\n";
    size = s.size;
    count = s.count;
    elements = new int[size];
    for (int i = 0; i < count; i++)
        elements[i] = s.elements[i];
}

stack::~stack() {
    cout << "--destructor called\n";
    delete[] elements;
}

void stack::push(int x) {
    if (count >= size)
        throw invalid_operation_ex();

    elements[count] = x;
    count++;
}

void stack::pop() {
    if (count > 0)
        count--;
    else
        throw invalid_operation_ex();
}

int stack::top() const {
    if (count > 0)
        return elements[count-1];
    else
        throw invalid_operation_ex();
}
void print_stack(stack s) {
    while (s.elem_count() > 0) {
        cout << s.top() << ' ';
        s.pop();
    }
    cout << endl;
}

int main() {
    stack u;
    u.push(4);
    u.push(5);
    u.push(12);
    print_stack(u);
    u.pop();
    u.pop();
    u.pop();
    // Explain the result of execution of this code
}
					
مشکل با عملگر جایگزینی

این مثال نشان می‌دهد برای استفاده‌ی بی‌اشکال از کلاسی که تخصیص حافظه‌ی پویا دارد لازم است به عملگر جایگزینی هم توجه شود.

#include <iostream>
using namespace std;

#define DEFAULT_SIZE 10

class invalid_operation_ex {};
//تعریف کلاس پشته مانند مثال قبل

class stack {
public:
    stack(int size);
	stack(const stack&);
    ~stack();
    void push(int x);
    void pop();
    int top() const;
    int elem_count() const { return count; }
private:
    int *elements;
    int size;
    int count;
};

stack::stack(int s = DEFAULT_SIZE) {
    cout << "--constructor called\n";
    size = s;
    elements = new int[size];
    count = 0;
}

stack::stack(const stack& s) {
    cout << "-- copy constructor called\n";
    size = s.size;
    count = s.count;
    elements = new int[size];
    for (int i = 0; i < count; i++)
        elements[i] = s.elements[i];
}

stack::~stack() {
    cout << "--destructor called\n";
    delete[] elements;
}

void stack::push(int x) {
    if (count >= size)
        throw invalid_operation_ex();

    elements[count] = x;
    count++;
}

void stack::pop() {
    if (count > 0)
        count--;
    else
        throw invalid_operation_ex();
}

int stack::top() const {
    if (count > 0)
        return elements[count-1];
    else
        throw invalid_operation_ex();
}

void print_stack(stack s) {
    while (s.elem_count() > 0) {
        cout << s.top() << ' ';
        s.pop();
    }
    cout << endl;
}

int main() {
    stack u;
    u.push(4);
    u.push(5);
    u.push(12);

    stack v = u;

    stack w;
    w = u;

    // Explain the result of execution of this code
}
					
سربارگذاری عملگر جایگزینی

برای رفع مشکل مثال قبل، باید عملگر جایگزینی را سربارگذاری کنیم.

#include <iostream>
using namespace std;

#define DEFAULT_SIZE 10

class invalid_operation_ex {};

class stack {
public:
    stack(int size);
    stack(const stack&);
    ~stack();
    stack& operator=(const stack&);
    //تعریف سایر اعضا مانند مثال قبل
    void push(int x);
    void pop();
    int top() const;
    int elem_count() const { return count; }
private:
    int *elements;
    int size;
    int count;
};

stack::stack(int s = DEFAULT_SIZE) {
    cout << "--constructor called\n";
    size = s;
    elements = new int[size];
    count = 0;
}

stack::stack(const stack& s) {
    cout << "-- copy constructor called\n";
    size = s.size;
    count = s.count;
    elements = new int[size];
    for (int i = 0; i < count; i++)
        elements[i] = s.elements[i];
}

stack::~stack() {
    cout << "--destructor called\n";
    delete[] elements;
}

stack& stack::operator=(const stack& s)
{
    if (this == &s)
        return *this;
        
    count = s.count;
    size = s.size;
    delete[] elements;
    elements = new int[size];
    for (int i = 0; i < count; i++)
        elements[i] = s.elements[i];
        
    return *this;
}

void stack::push(int x) {
    if (count >= size)
        throw invalid_operation_ex();

    elements[count] = x;
    count++;
}

void stack::pop() {
    if (count > 0)
        count--;
    else
        throw invalid_operation_ex();
}

int stack::top() const {
    if (count > 0)
        return elements[count-1];
    else
        throw invalid_operation_ex();
}

void print_stack(stack s) {
    while (s.elem_count() > 0) {
        cout << s.top() << ' ';
        s.pop();
    }
    cout << endl;
}

int main() {
    stack u;
    u.push(4);
    u.push(5);
    u.push(12);

    stack w;
    w = u;
}
					
تمرین‌های کوتاه
  1. تمرین ۵ از بخش سربارگذاری عملگرها (کلاس BigInt) را مجدداً انجام دهید با این شرط که ارقام را به جای بردار در آرایه‌ای که به طور پویا تخصیص یافته نگهدارید. اعضای کلاس را به طور کامل پیاده‌سازی کنید.
  2. در صورتی که در سربارگذاری عملگر جایگزینی فراموش کنیم شرط ابتدای آن را بنویسیم، چه اتفاقی می‌افتد؟