2015-01-07 14 views
25
T& f() { // some code ... } 
const T& f() const { // some code ... } 

Tôi đã nhìn thấy điều này một vài lần ngay bây giờ (trong cuốn sách giới thiệu mà tôi đã nghiên cứu cho đến nay). Tôi biết rằng const đầu tiên làm cho giá trị trả về const, nói cách khác: unmodifiable. Const thứ hai cho phép hàm có thể được gọi cho các biến được khai báo const, tôi tin.Cùng chức năng với const và không có - Khi nào và tại sao?

Nhưng tại sao bạn có cả hai hàm trong một và cùng một định nghĩa lớp? Và trình biên dịch phân biệt như thế nào? Tôi tin rằng thứ hai f() (với const) có thể được gọi cho các biến không phải là const.

+0

Gợi ý: người đầu tiên cho phép bạn sửa đổi bất cứ điều gì ' f() 'trả về, có thể là một phần của đối tượng' f() 'đang được gọi. – juanchopanza

+2

Thứ hai không được phép sửa đổi trạng thái của lớp mà phương thức này được xác định. – kerem

+5

http://www.parashift.com/c++-faq/const-overloading.html –

Trả lời

21

Nhưng tại sao bạn có cả hai hàm trong cùng một định nghĩa lớp học?

Có cả cho phép bạn:

  • cuộc gọi chức năng trên một đối tượng có thể thay đổi và sửa đổi kết quả nếu bạn thích; và
  • gọi hàm trên đối tượng const và chỉ xem kết quả.

Chỉ với lần đầu tiên, bạn không thể gọi nó trên đối tượng const. Chỉ với lần thứ hai, bạn không thể sử dụng nó để sửa đổi đối tượng mà nó trả về một tham chiếu đến.

Và trình biên dịch phân biệt như thế nào?

Nó chọn quá tải const khi hàm được gọi trên const đối tượng (hoặc thông qua một tham chiếu hoặc con trỏ đến const). Nó chọn quá tải khác.

Tôi tin rằng f() thứ hai (với const) thứ hai có thể được gọi cho các biến không phải là const.

Nếu đó chỉ là tình trạng quá tải thì có thể. Với cả hai tình trạng quá tải, thay vào đó, quá tải không phải là const sẽ được chọn.

5

Người đầu tiên không có const cho phép người gọi sửa đổi đối tượng, nói chung là một thành viên của lớp có phương thức đang được gọi.

Thứ hai, nơi lớp máy chủ của chúng tôi ở chế độ chỉ đọc, cũng cho phép truy cập chỉ đọc vào thành viên của nó.

Theo mặc định, phiên bản không phải const được gọi nếu được phép theo các quy tắc của constness.

Một trong những ví dụ phổ biến nhất về điều này là với một số loại lớp thu thập/loại mảng.

class Array 
{ 
    private: 
     MyType members[MySize]; 

    public: 
     MyType & operator[](size_t index); 
     const MyType & operator[](size_t index) const; 
}; 

Giả sử chúng được triển khai và có thể là mẫu hoặc chúng là loại và kích thước cụ thể. Tôi đang chứng minh quá tải const.

Bây giờ chúng tôi có thể nhờ ai đó sử dụng lớp học. Bạn có thể muốn đặt giá trị.

Array myArray; 
myArray[ 3 ] = myObject; 

Hoặc bạn có thể chỉ đọc nó:

const Array& myArrayRef = getArrayRef(); // gets it to read 
const MyType & myValueRef = myArrayRef[ 3 ]; 

Vì vậy, bạn thấy tôi có thể sử dụng các ký hiệu để cả hai thiết lập một giá trị và đọc một. Như với operator[], bạn có thể áp dụng kỹ thuật này cho bất kỳ phương pháp nào.

12

Nhưng tại sao bạn có cả hai hàm trong một và cùng một định nghĩa lớp?

Đôi khi bạn muốn cung cấp ngữ nghĩa khác nhau cho cùng một hoạt động phụ thuộc vào việc nó được gọi trên đối tượng const HOẶC non-const đối tượng. Hãy lấy một ví dụ về std::string lớp: -

char& operator[](int index); 
const char& operator[](int index) const; 

Trong trường hợp này khi operator[] gọi qua const đối tượng bạn sẽ không cho phép người dùng thay đổi các nội dung của chuỗi.

const std::string str("Hello"); 
str[1] = 'A';  // You don't want this for const. 

Mặt khác, trong trường hợp chuỗi không const bạn cho phép người dùng thay đổi nội dung của chuỗi. Đó là lý do tại sao một tình trạng quá tải khác.

Và trình biên dịch phân biệt như thế nào?

Trình biên dịch kiểm tra xem phương thức đó được gọi trên đối tượng const HOẶC non-const và sau đó gọi phương thức đó một cách thích hợp.

const std::string str("Hello"); 
cout << str[1];   // Invokes `const` version. 

std::string str("Hello"); 
cout << str[1];   // Invokes non-const version. 
3

Nó cho phép cả hai có quyền truy cập vào dữ liệu cá thể const theo cách chỉ đọc, trong khi vẫn có thể sửa đổi dữ liệu cá thể không const.

#include <iostream> 

class test 
{ 
    public: 
    test() : data_(0) {} 

    int& f() { return data_; } 
    const int& f() const { return data_ } 

    private: 
    int data_; 
}; 

int main(void) 
{ 
    const test rock; 
    test paper; 

    /* we can print both */ 
    std::cout << rock.f() << std::endl; 
    std::cout << paper.f() << std::endl; 

    /* but we can modify only the non const one */ 
    // rock.f() = 21; 
    paper.f() = 42; 

} 
4

Các vòng loại sau khi Parens chức năng cuộc gọi áp dụng đối với các ẩn this tham số của hàm thành viên:

Một chức năng thành viên void Foo::bar() là loại như thế này: void bar(Foo *this). Nhưng điều gì sẽ xảy ra nếu đối tượng Foo là const?

struct Foo { 
    void bar(); 
}; 

const Foo f{}; 
f.bar(); 

Vâng, kể từ Foo::bar() mất một tham số Foo *this, mà không được phép được const, trên f.bar(); thất bại trong việc biên dịch. Vì vậy, chúng ta cần một cách để đủ điều kiện tham số ẩn this, và cách C++ chọn để làm điều đó là để cho phép các vòng loại đó đi ra ngoài hàm parens.

Cách trình biên dịch phân biệt các hàm này giống hệt nhau theo mọi cách để quá tải hàm bình thường, vì đó chính xác là gì, mặc dù cú pháp lạ.

Hơn nữa, const không phải là vòng loại duy nhất. Bạn cũng có thể thêm volatile vòng loại và trong C++ 11, bạn cũng có thể đặt vòng loại tham chiếu lvalue và rvalue.


Lý do chúng tôi cần hai bản sao gần như giống hệt nhau của hàm này là vì không có cách trực tiếp để rút ra một sự khác biệt duy nhất: các loại trả về khác nhau. Nếu chúng ta có một đối tượng const, và đối tượng đó có một getter trả về một tham chiếu đến một cái gì đó nó chứa, tham chiếu đó cần phải đủ điều kiện giống như đối tượng tổng thể.

struct Foo { 
    int i; 
    int &get_i() const { return i; } 
}; 

int main() { 
    const Foo f{}; 
    f.get_i() = 10; // i should be const! 
} 

trên sẽ thậm chí không biên dịch vì bên Foo::get_i() const, i là const, và chúng tôi không thể trả về một tham chiếu không const với nó. Nhưng nếu nó được cho phép, nó sẽ sai vì chúng ta không thể sửa đổi các thành viên của một đối tượng const. Vì vậy, Foo::get_i() const cần phải trả lại tham chiếu const thành i.

int const &Foo::get_i() const { return i; } 

Nhưng chúng ta nên có thể sửa đổi một thành viên của một đối tượng không const,

int main() { 
    Foo f{}; 
    f.get_i() = 10; // should be fine 
} 

vì vậy chúng tôi không thể chỉ có chức năng này. Chúng ta cần một hàm trả về tham chiếu không const khi đối tượng Foo không phải là const. Vì vậy, chúng tôi quá tải chức năng dựa trên const-Ness của đối tượng:

struct Foo { 
    int i; 
    int const &get_i() const { return i; } 
    int &get_i() { return i; } 
}; 

Nếu cơ quan chức năng được nhiều phức tạp có một lựa chọn có thể để tránh trùng lặp:

struct Foo { 
    int i; 
    int const &get_i() const { return i; } 

    int &get_i() { return const_cast<int &>(const_cast<Foo const *>(this)->get_i()); } 
}; 

Đó là, các phi -const quá tải đại biểu thực hiện của nó để quá tải const, bằng cách sử dụng const_cast để sửa chữa các loại. Thêm const luôn an toàn. Việc xóa const bằng cách sử dụng const_cast chỉ an toàn khi chúng ta biết chắc chắn rằng đối tượng ban đầu không phải là const. Chúng ta biết rằng trong trường hợp này, bởi vì chúng ta biết chúng ta đã thêm const vào vị trí đầu tiên cho một đối tượng không phải const.

4

Có một ví dụ rất hay trong khung công tác Qt.

Hãy xem qua lớp QImage.

Có hai chức năng công cộng:

const uchar* scanLine (int i) const; 
uchar* scanLine (int i); 

Người đầu tiên chỉ dành cho truy cập đọc. Điều thứ hai là đối với trường hợp bạn muốn sửa đổi đường quét.

Tại sao sự khác biệt này lại quan trọng? Vì Qt sử dụng implicit data sharing. Điều này có nghĩa, QImage không ngay lập tức thực hiện một bản sao sâu nếu bạn làm điều gì đó như thế này:

QImage i1, i2; 
i1.load("image.bmp"); 
i2 = i1;      // i1 and i2 share data 

Thay vào đó, dữ liệu được sao chép chỉ và chỉ khi bạn gọi một chức năng mà thực sự làm thay đổi một trong hai hình ảnh, giống như non-const scanLine.

3

Như đã đề cập trước đó, bạn có thể sử dụng các phiên bản const và không phải const tùy thuộc vào const-ness của đối tượng gọi. Mô hình này thường được sử dụng với operator[] cho mảng.Một cách để tránh việc lặp lại code (lấy từ cuốn sách Scott Meyers' Effective C++) là const_cast chức năng const trở lại trong một tình trạng quá tải không const, chẳng hạn như:

// returns the position of some internal char array in a class Foo 
const char& Foo::operator[](std::size_t position) const 
{ 
    return arr[position]; // getter 
} 

// we now define the non-const in terms of the const version 
char& Foo::operator[](std::size_t position) 
{ 
    return const_cast<char&>(// cast back to non-const 
     static_cast<const Foo&>(*this)[position] // calls const overload 
    ); // getter/setter 
} 
Các vấn đề liên quan