Tính năng ẩn của C ++? [đóng cửa]


114

Không yêu C ++ khi nói đến dòng câu hỏi "tính năng ẩn của"? Tôi sẽ ném nó ra khỏi đó. Một số tính năng ẩn của C ++ là gì?


@Devtron - Tôi đã thấy một số lỗi tuyệt vời (tức là hành vi không mong muốn) được bán dưới dạng tính năng. Trên thực tế, ngành công nghiệp trò chơi thực sự đang cố gắng biến điều này thành hiện thực và gọi nó là "trò chơi mới nổi" (ngoài ra, hãy xem "TK Surfing" từ Psi-Ops, hoàn toàn là một lỗi, sau đó họ để nguyên như vậy và một trong những các tính năng tốt nhất của trò chơi IMHO)
Grant Peters

5
@Laith J: Không có nhiều người đã đọc tiêu chuẩn ISO C ++ dài 786 trang từ đầu đến cuối - nhưng tôi cho rằng bạn đã có, và bạn đã giữ lại tất cả, đúng không?
j_random_hacker 14/02/10

2
@Laith, @j_random: Xem câu hỏi của tôi "Trò đùa của lập trình viên là gì, làm cách nào để nhận ra nó và câu trả lời thích hợp là gì" tại stackoverflow.com/questions/1/you-have-been-link-rolled .

Câu trả lời:


308

Hầu hết các lập trình viên C ++ đều quen thuộc với toán tử bậc ba:

x = (y < 0) ? 10 : 20;

Tuy nhiên, họ không nhận ra rằng nó có thể được sử dụng như một lvalue:

(a == 0 ? a : b) = 1;

đó là viết tắt của

if (a == 0)
    a = 1;
else
    b = 1;

Sử dụng cẩn thận :-)


11
Rất thú vị. Tôi có thể thấy rằng việc tạo ra một số mã không thể đọc được.
Jason Baker

112
Rất tiếc. (a == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Dunno nếu nó GCC cụ thể, nhưng tôi đã ngạc nhiên khi tìm thấy điều này cũng làm việc: (value ? function1 : function2)().
Chris Burt-Brown

3
@Chris Burt-Brown: Không, điều đó sẽ hoạt động ở mọi nơi nếu chúng có cùng kiểu (tức là không có đối số mặc định) function1function2được chuyển đổi hoàn toàn thành con trỏ hàm, và kết quả hoàn toàn được chuyển đổi ngược lại.
MSalters

238

Bạn có thể đặt URI vào mã nguồn C ++ mà không bị lỗi. Ví dụ:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Nhưng chỉ có một cho mỗi chức năng, tôi nghi ngờ? :)
Constantin

51
@jpoh: http theo sau dấu hai chấm sẽ trở thành "nhãn" mà bạn sử dụng trong câu lệnh goto sau này. bạn nhận được cảnh báo đó từ trình biên dịch của mình vì nó không được sử dụng trong bất kỳ câu lệnh goto nào trong ví dụ trên.
utku_karatas

9
Bạn có thể thêm nhiều hơn một, miễn là chúng có các giao thức khác nhau! ftp.microsoft.com gopher: //aerv.nl/1, v.v.
Daniel Earwicker

4
@Pavel: Một định danh theo sau dấu hai chấm là một nhãn (để sử dụng với gotoC ++). Bất cứ điều gì sau hai dấu gạch chéo là một nhận xét. Do đó, với http://stackoverflow.com, httplà một nhãn (về mặt lý thuyết, bạn có thể viết goto http;), và //stackoverflow.comchỉ là một nhận xét cuối dòng. Cả hai đều là C ++ hợp pháp, vì vậy cấu trúc sẽ biên dịch. Tất nhiên, nó không làm bất cứ điều gì hữu ích một cách mơ hồ.
David Thornley

8
Thật không may, goto http;nó không thực sự theo sau URL. :(
Yakov Galka

140

Số học con trỏ.

Các lập trình viên C ++ thích tránh con trỏ vì các lỗi có thể được đưa vào.

C ++ thú vị nhất mà tôi từng thấy? Chữ tương tự.


11
Chúng tôi tránh con trỏ vì lỗi? Con trỏ về cơ bản là tất cả mọi thứ mà mã hóa C ++ động là về!
Nick Bedford

1
Các ký tự tương tự rất tuyệt vời cho các bài dự thi C ++ bị xáo trộn, đặc biệt là loại ASCII-art.
Synetech

119

Tôi đồng ý với hầu hết các bài viết ở đó: C ++ là một ngôn ngữ đa mô hình, vì vậy các tính năng "ẩn" mà bạn sẽ tìm thấy (ngoài "các hành vi không xác định" mà bạn nên tránh bằng mọi giá) là cách sử dụng thông minh của các phương tiện.

Hầu hết các phương tiện đó không phải là các tính năng tích hợp của ngôn ngữ mà là các tính năng dựa trên thư viện.

Quan trọng nhất là RAII , thường bị các nhà phát triển C ++ đến từ thế giới C bỏ qua trong nhiều năm. Nạp chồng toán tử thường là một tính năng bị hiểu nhầm cho phép cả hành vi giống mảng (toán tử chỉ số con), hoạt động giống như con trỏ (con trỏ thông minh) và hoạt động giống như xây dựng (nhân ma trận).

Việc sử dụng ngoại lệ thường khó khăn, nhưng với một số công việc, có thể tạo ra mã thực sự mạnh mẽ thông qua an toàn ngoại lệ số kỹ thuật (bao gồm mã sẽ không bị lỗi hoặc sẽ có các tính năng giống như cam kết sẽ thành công hoặc hoàn nguyên trở lại trạng thái ban đầu của nó).

Tính năng "ẩn" nổi tiếng nhất của C ++ là lập trình siêu mẫu , vì nó cho phép bạn thực thi một phần (hoặc toàn bộ) chương trình của mình tại thời điểm biên dịch thay vì thời gian chạy. Tuy nhiên, điều này rất khó và bạn phải nắm chắc các mẫu trước khi thử.

Người khác sử dụng nhiều mô hình để tạo ra "cách lập trình" bên ngoài tổ tiên của C ++, tức là C.

Bằng cách sử dụng functors , bạn có thể mô phỏng chức năng, với thêm kiểu an toàn và là stateful. Sử dụng mẫu lệnh , bạn có thể trì hoãn việc thực thi mã. Hầu hết các mẫu thiết kế khác có thể được triển khai dễ dàng và hiệu quả trong C ++ để tạo ra các kiểu mã hóa thay thế không được cho là nằm trong danh sách "các mô hình C ++ chính thức".

Bằng cách sử dụng các mẫu , bạn có thể tạo ra mã sẽ hoạt động trên hầu hết các loại, kể cả loại mã mà bạn nghĩ lúc đầu. Bạn cũng có thể tăng độ an toàn của kiểu (như an toàn kiểu tự động malloc / realloc / free). Các tính năng của đối tượng C ++ thực sự mạnh mẽ (và do đó, nguy hiểm nếu sử dụng bất cẩn), nhưng ngay cả tính đa hình động cũng có phiên bản tĩnh của nó trong C ++: CRTP .

Tôi nhận thấy rằng hầu hết các sách kiểu " C ++ hiệu quả " của Scott Meyers hoặc sách kiểu " C ++ đặc biệt " từ Herb Sutter đều dễ đọc và có kho thông tin về các tính năng đã biết và ít được biết đến của C ++.

Trong số ưu tiên của tôi là một điều khiến bất kỳ lập trình viên Java nào cũng phải kinh hãi: Trong C ++, cách hướng đối tượng tốt nhất để thêm một tính năng vào một đối tượng là thông qua một hàm non-member non-friend, thay vì một member- hàm (tức là phương thức lớp), bởi vì:

  • Trong C ++, giao diện của một lớp 'vừa là các hàm thành viên vừa là các hàm không phải thành viên trong cùng một không gian tên

  • chức năng non-friend non-member không có quyền truy cập đặc quyền vào nội bộ lớp. Do đó, việc sử dụng một hàm thành viên thay cho một hàm không phải là thành viên không phải là bạn bè sẽ làm suy yếu tính đóng gói của lớp.

Điều này không bao giờ làm ngạc nhiên ngay cả những nhà phát triển có kinh nghiệm.

(Nguồn: Trong số những người khác, Guru trực tuyến của Herb Sutter của Tuần # 84: http://www.gotw.ca/gotw/084.htm )


+1 câu trả lời rất kỹ lưỡng. nó không đầy đủ vì những lý do rõ ràng (nếu không sẽ không có "tính năng ẩn" nữa!): p ở điểm đầu tiên ở cuối câu trả lời, bạn đã đề cập đến các thành viên của giao diện lớp. ý bạn là ".. là cả hàm thành viên và chức năng bạn bè không phải là thành viên"?
wilhelmtell 3/10/08


những gì bạn đang đề cập đến với 1 phải là tra cứu koenig, phải không?
Özgür

1
@wilhelmtell: Không không không ... :-p ... Ý tôi là "các chức năng thành viên của nó và các chức năng không phải thành viên NON-FRIEND" .... Koenig's Lookup sẽ đảm bảo các chức năng này sẽ được xem xét sớm hơn các chức năng khác " bên ngoài "chức năng tìm kiếm các ký hiệu
paercebal

7
Bài đăng tuyệt vời và +1 đặc biệt cho phần cuối, điều mà quá ít người nhận ra. Tôi có lẽ cũng muốn thêm thư viện Boost làm "tính năng ẩn". Tôi khá coi nó là thư viện chuẩn mà C ++ nên có. ;)
jalf

118

Một đặc điểm ngôn ngữ mà tôi cho là hơi bị ẩn, vì tôi chưa bao giờ nghe nói về nó trong suốt thời gian đi học, là bí danh không gian tên. Nó không được chú ý cho đến khi tôi tìm thấy các ví dụ về nó trong tài liệu tăng cường. Tất nhiên, bây giờ tôi biết về nó, bạn có thể tìm thấy nó trong bất kỳ tài liệu tham khảo C ++ chuẩn nào.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
Tôi đoán điều này hữu ích nếu bạn không muốn sử dụng using.
Siqi Lin

4
Nó cũng hữu ích như một cách để chuyển đổi giữa hiện thực, cho dù lựa chọn nói thread-safe so với người không thread-safe, hoặc phiên bản 1 so với 2.
Tony Delroy

3
Nó đặc biệt hữu ích nếu bạn đang làm việc trên một dự án rất lớn với hệ thống phân cấp không gian tên lớn và bạn không muốn tiêu đề của mình gây ô nhiễm không gian tên (và bạn muốn các khai báo biến của mình có thể đọc được).
Brandon Bohrer

102

Các biến không chỉ có thể được khai báo trong phần init của một forvòng lặp mà còn cả các lớp và hàm.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Điều đó cho phép nhiều biến thuộc các kiểu khác nhau.


31
Rất vui khi biết rằng bạn có thể làm điều đó, nhưng cá nhân tôi thực sự cố gắng tránh làm bất cứ điều gì như vậy. Chủ yếu là vì nó khó đọc.
Zoomulator

2
Trên thực tế, những gì sẽ hoạt động trong ngữ cảnh này là sử dụng một cặp: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin tốt thì tôi khuyên bạn nên thử và thực hiện báo cáo lỗi đối với VS2008 thay vì phản đối tính năng ẩn. Đó rõ ràng là lỗi của trình biên dịch của bạn.
Johannes Schaub - litb

2
Rất tiếc, nó cũng không hoạt động trong msvc10. Buồn làm sao :(
avakar

2
@avakar trên thực tế, gcc đã giới thiệu một lỗi khiến nó cũng từ chối, trong v4.6 :) xem gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

Toán tử mảng là liên kết.

A [8] là một từ đồng nghĩa với * (A + 8). Vì phép cộng có tính chất kết hợp, nên có thể được viết lại thành * (8 + A), là từ đồng nghĩa với ..... 8 [A]

Bạn không nói hữu ích ... :-)


15
Trên thực tế, khi sử dụng thủ thuật này, bạn nên thực sự chú ý đến loại bạn đang sử dụng. A [8] thực sự là A thứ 8 trong khi 8 [A] là số nguyên Ath bắt đầu từ địa chỉ 8. Nếu A là một byte, bạn có một lỗi.
Vincent Robert

38
bạn có nghĩa là "giao hoán" trong khi bạn nói "liên kết"?
DarenW 17/09/08

28
Vincent, bạn sai rồi. Loại Akhông quan trọng ở tất cả. Ví dụ, nếu Alà a char*, mã sẽ vẫn hợp lệ.
Konrad Rudolph

11
Lưu ý rằng A phải là một con trỏ, và không phải là một toán tử nạp chồng lớp [].
David Rodríguez - dribeas

15
Vincent, trong cái này phải có một kiểu tích phân và một kiểu con trỏ, và cả C và C ++ đều không quan tâm cái nào đi trước.
David Thornley

73

Một điều ít người biết là các công đoàn cũng có thể là các khuôn mẫu:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

Và chúng cũng có thể có các hàm tạo và các hàm thành viên. Chỉ không có gì liên quan đến kế thừa (bao gồm cả các chức năng ảo).


Hấp dẫn! Vì vậy, bạn phải khởi tạo tất cả các thành viên? Nó có tuân theo thứ tự cấu trúc thông thường, ngụ ý rằng thành viên cuối cùng sẽ được khởi tạo "trên đầu" các thành viên trước đó không?
j_random_hacker

j_random_hacker ồ, đúng là vô nghĩa. nắm bắt tốt. tôi đã viết nó vì nó sẽ là một cấu trúc. chờ đợi tôi sẽ sửa chữa nó
Johannes Schaub - litb

Điều này có gọi là hành vi không xác định không?
Greg Bacon

7
@gbacon, vâng nó invoke hành vi undefined nếu FromTođược thiết lập và sử dụng cho phù hợp. Tuy nhiên, kết hợp như vậy có thể được sử dụng với hành vi đã xác định (với Toviệc là một mảng các char không dấu hoặc một cấu trúc chia sẻ một chuỗi ban đầu với From). Ngay cả khi bạn sử dụng nó theo cách không xác định, nó vẫn có thể hữu ích cho công việc cấp thấp. Dù sao, đây chỉ là một ví dụ về mẫu liên minh - có thể có những cách sử dụng khác cho liên minh mẫu.
Johannes Schaub - litb

3
Cẩn thận với nhà xây dựng. Lưu ý rằng bạn được yêu cầu chỉ tạo phần tử đầu tiên và nó chỉ được phép trong C ++ 0x. Theo tiêu chuẩn hiện tại, bạn phải tuân theo các kiểu xây dựng tầm thường. Và không có trình hủy.
Potatoswatter

72

C ++ là một tiêu chuẩn, không nên có bất kỳ tính năng ẩn nào ...

C ++ là một ngôn ngữ đa mô hình, bạn có thể đặt cược số tiền cuối cùng của mình vào đó là các tính năng ẩn. Một ví dụ trong số nhiều ví dụ: lập trình siêu mẫu theo mẫu . Không ai trong ủy ban tiêu chuẩn có ý định có một ngôn ngữ con hoàn chỉnh Turing được thực thi tại thời điểm biên dịch.


65

Một tính năng ẩn khác không hoạt động trong C là chức năng của toán +tử một ngôi. Bạn có thể sử dụng nó để quảng bá và phân rã mọi thứ

Chuyển đổi một bảng kê thành một số nguyên

+AnEnumeratorValue

Và giá trị kiểu liệt kê của bạn trước đây có kiểu liệt kê giờ có kiểu số nguyên hoàn hảo có thể phù hợp với giá trị của nó. Theo cách thủ công, bạn sẽ khó biết loại đó! Điều này là cần thiết, chẳng hạn khi bạn muốn triển khai toán tử nạp chồng cho kiểu liệt kê của mình.

Lấy giá trị từ một biến

Bạn phải sử dụng một lớp sử dụng bộ khởi tạo tĩnh trong lớp mà không có định nghĩa ngoài lớp, nhưng đôi khi nó không liên kết được? Nhà điều hành có thể giúp tạo một tạm thời mà không cần đưa ra các giả định hoặc phụ thuộc vào loại của nó

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

Phân rã một mảng thành một con trỏ

Bạn có muốn chuyển hai con trỏ đến một hàm nhưng nó không hoạt động không? Nhà điều hành có thể giúp

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

Thời gian tồn tại của thời gian tạm thời bị ràng buộc với tham chiếu const là một trong những điều mà ít người biết đến. Hoặc ít nhất đó là phần kiến ​​thức C ++ yêu thích của tôi mà hầu hết mọi người đều không biết.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
Bạn có thể xây dựng? Như thể bạn chỉ đang trêu chọc thôi;)
Joseph Garvin

8
ScopeGuard ( ddj.com/cpp/184403758 ) là một ví dụ tuyệt vời tận dụng tính năng này.
MSN

2
Tôi với Joseph Garvin. Xin hãy soi sáng cho chúng tôi.
Peter Mortensen

Tôi chỉ làm trong các ý kiến. Bên cạnh đó, đó là hệ quả tự nhiên của việc sử dụng tham số tham chiếu const.
MSN


52

Một tính năng thú vị không được sử dụng thường xuyên là khối try-catch toàn hàm:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

Cách sử dụng chính sẽ là dịch ngoại lệ sang lớp ngoại lệ khác và rethrow, hoặc dịch giữa các ngoại lệ và xử lý mã lỗi dựa trên trả về.


Tôi không nghĩ rằng bạn có thể returnbắt được khối chức năng Thử, chỉ có thể phát lại.
Constantin

Tôi vừa thử biên dịch ở trên, và nó không đưa ra cảnh báo. Tôi nghĩ rằng ví dụ trên hoạt động.
livingos

7
return chỉ bị cấm đối với các constructor. Khối try trong hàm của một hàm tạo sẽ bắt lỗi khi khởi tạo cơ sở và các thành viên (trường hợp duy nhất mà khối try của hàm thực hiện điều gì đó khác với việc chỉ thử bên trong hàm); không ném lại sẽ dẫn đến một đối tượng không hoàn chỉnh.
puetzk

Đúng. Điều này rất hữu ích. Tôi đã viết macro BEGIN_COM_METHOD và END_COM_METHOD để bắt các ngoại lệ và trả về HRESULTS để các ngoại lệ không bị rò rỉ ra khỏi một lớp triển khai giao diện COM. Nó hoạt động tốt.
Scott Langham

3
Như đã chỉ ra bởi @puetzk, đây là cách duy nhất để xử lý các ngoại lệ được ném bởi bất kỳ thứ gì trong danh sách khởi tạo của phương thức khởi tạo , chẳng hạn như các hàm tạo của lớp cơ sở hoặc của các thành viên dữ liệu.
anton.burger

44

Nhiều người biết về identity/ idmeta Chức năng, nhưng có một công dụng tuyệt vời dành cho nó đối với các trường hợp không phải khuôn mẫu:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

Nó giúp giải mã các khai báo C ++ rất nhiều!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

Thật thú vị, nhưng ban đầu tôi thực sự gặp khó khăn hơn khi đọc một số định nghĩa đó. Một cách khác để giải quyết vấn đề từ trong ra ngoài với tờ khai C ++ là viết một số bí danh mẫu loại: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);hay pointer<void(int)> f(pointer<void()>);hayfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

Một tính năng khá ẩn là bạn có thể xác định các biến trong điều kiện if và phạm vi của nó sẽ chỉ trải dài trên if và các khối khác của nó:

if(int * p = getPointer()) {
    // do something
}

Ví dụ: một số macro sử dụng điều đó để cung cấp một số phạm vi "bị khóa" như sau:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

Ngoài ra BOOST_FOREACH sử dụng nó dưới mui xe. Để hoàn thành việc này, bạn không chỉ có thể thực hiện được trong if mà còn có thể thực hiện được trong switch:

switch(int value = getIt()) {
    // ...
}

và trong một vòng lặp while:

while(SomeThing t = getSomeThing()) {
    // ...
}

(và cả trong một điều kiện). Nhưng tôi không chắc liệu những thứ này có hữu ích hay không :)


Khéo léo! Tôi chưa bao giờ biết bạn có thể làm điều đó ... nó sẽ (và sẽ) tiết kiệm một số rắc rối khi viết mã với các giá trị trả về lỗi. Có cách nào để vẫn có một điều kiện thay vì chỉ! = 0 ở dạng này không? if ((int r = func ()) <0) dường như không làm việc ...
puetzk

puetzk, không có. nhưng rất vui vì bạn thích nó :)
Johannes Schaub - litb

4
@Frerich, điều này không thể thực hiện được trong mã C. Tôi nghĩ bạn đang nghĩ đến if((a = f()) == b) ..., nhưng câu trả lời này thực sự khai báo một biến trong điều kiện.
Johannes Schaub - litb

1
@Angry nó rất khác, vì khai báo biến được kiểm tra giá trị boolean của nó ngay lập tức. Có một ánh xạ đến các vòng lặp, có vẻ như for(...; int i = foo(); ) ...;Điều này sẽ đi qua phần thân miễn ilà đúng, khởi tạo lại nó mỗi lần. Vòng lặp mà bạn thấy chỉ đơn giản là thể hiện một tuyên bố thay đổi, nhưng không phải là một lời tuyên bố biến mà simultanuously đóng vai trò như một điều kiện :)
Johannes Schaub - litb

5
Rất tốt, ngoại trừ việc bạn không đề cập mục đích sử dụng của tính năng này là cho phôi con trỏ động, tôi tin.
mmocny

29

Ngăn nhà điều hành dấu phẩy khỏi tình trạng quá tải nhà điều hành cuộc gọi

Đôi khi bạn sử dụng hợp lệ toán tử dấu phẩy, nhưng bạn muốn đảm bảo rằng không có toán tử dấu phẩy nào do người dùng xác định gây cản trở, vì ví dụ: bạn dựa vào các điểm trình tự giữa bên trái và bên phải hoặc muốn đảm bảo không có gì cản trở điều mong muốn hoạt động. Đây là nơi void()xuất hiện trong trò chơi:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Bỏ qua những người giữ chỗ mà tôi đặt cho điều kiện và mã. Điều quan trọng là void(), khiến trình biên dịch buộc phải sử dụng toán tử dấu phẩy nội trang. Điều này cũng có thể hữu ích khi triển khai các lớp đặc điểm.


Tôi chỉ sử dụng điều này để kết thúc biểu hiện bỏ qua biểu hiện quá mức cần thiết của mình . :)
GManNickG

28

Khởi tạo mảng trong phương thức khởi tạo. Ví dụ trong một lớp nếu chúng ta có một mảng intlà:

class clName
{
  clName();
  int a[10];
};

Chúng ta có thể khởi tạo tất cả các phần tử trong mảng thành mặc định của nó (ở đây tất cả các phần tử của mảng bằng 0) trong hàm tạo như:

clName::clName() : a()
{
}

6
Bạn có thể làm điều này với bất kỳ mảng nào ở bất kỳ đâu.
Potatoswatter

@Potatoswatter: khó hơn vẻ ngoài, do phân tích cú pháp khó chịu nhất. Tôi không thể nghĩ ra bất cứ nơi nào khác nó có thể được thực hiện, ngoại trừ có lẽ một giá trị trả về
Mooing Duck

Nếu kiểu của mảng là kiểu lớp thì điều này không cần thiết phải không?
Thomas Eding

27

Oooh, tôi có thể đưa ra một danh sách những điều mà thú cưng ghét thay thế:

  • Bộ hủy cần phải ảo nếu bạn có ý định sử dụng đa hình
  • Đôi khi các thành viên được khởi tạo theo mặc định, đôi khi họ không
  • Không thể sử dụng các clases cục bộ làm thông số mẫu (làm cho chúng ít hữu ích hơn)
  • chỉ định ngoại lệ: trông hữu ích, nhưng không
  • quá tải hàm ẩn các hàm lớp cơ sở với các chữ ký khác nhau.
  • không có tiêu chuẩn hữu ích về quốc tế hóa (bộ ký tự rộng tiêu chuẩn di động, bất kỳ ai? Chúng tôi sẽ phải đợi cho đến khi C ++ 0x)

Về mặt tích cực

  • tính năng ẩn: chức năng thử khối. Thật không may, tôi đã không tìm thấy một công dụng cho nó. Có, tôi biết tại sao họ thêm nó, nhưng bạn phải cài đặt lại một hàm tạo khiến nó trở nên vô nghĩa.
  • Cần xem xét cẩn thận các đảm bảo của STL về tính hợp lệ của trình lặp sau khi sửa đổi vùng chứa, điều này có thể cho phép bạn tạo một số vòng lặp đẹp hơn một chút.
  • Boost - hầu như không phải là một bí mật nhưng nó rất đáng để sử dụng.
  • Tối ưu hóa giá trị trả về (không rõ ràng, nhưng nó được tiêu chuẩn cho phép cụ thể)
  • Functors hay còn gọi là đối tượng hàm hay còn gọi là operator (). Điều này được sử dụng rộng rãi bởi STL. không thực sự là một bí mật, nhưng là một tác dụng phụ tiện lợi của việc nạp chồng toán tử và các mẫu.

16
ghét thú cưng: không có ABI được xác định cho các ứng dụng C ++, không giống như các ứng dụng C mà mọi người sử dụng vì mọi ngôn ngữ đều có thể đảm bảo gọi một hàm C, không ai có thể làm như vậy đối với C ++.
gbjbaanb 25/09/08

8
Kẻ hủy diệt chỉ cần là ảo nếu bạn có ý định phá hủy đa hình, khác một chút tinh tế so với điểm đầu tiên.
David Rodríguez - dribeas

2
Với C ++, các kiểu cục bộ 0x có thể được sử dụng làm tham số mẫu.
tstenner

1
Với C ++ 0x, hàm hủy sẽ là ảo nếu đối tượng có bất kỳ hàm ảo nào (tức là vtable).
Macke

đừng quên NRVO, và tất nhiên mọi tối ưu hóa đều được phép miễn là nó không thay đổi đầu ra chương trình
jk.

26

Bạn có thể truy cập dữ liệu được bảo vệ và các thành viên chức năng của bất kỳ lớp nào, không có hành vi không xác định và với ngữ nghĩa mong đợi. Đọc tiếp để xem làm thế nào. Đọc thêm báo cáo khiếm khuyết về điều này.

Thông thường, C ++ cấm bạn truy cập vào các thành viên được bảo vệ không tĩnh của đối tượng của lớp, ngay cả khi lớp đó là lớp cơ sở của bạn

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

Điều đó bị cấm: Bạn và trình biên dịch không biết tham chiếu thực sự trỏ đến cái gì. Nó có thể là một Cđối tượng, trong trường hợp đó lớp Bkhông có nghiệp vụ và manh mối về dữ liệu của nó. Quyền truy cập như vậy chỉ được cấp nếu xlà một tham chiếu đến một lớp dẫn xuất hoặc một lớp dẫn xuất từ ​​nó. Và nó có thể cho phép đoạn mã tùy ý đọc bất kỳ thành viên được bảo vệ nào bằng cách tạo một lớp "vứt bỏ" để đọc ra các thành viên, ví dụ như std::stack:

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Chắc chắn, như bạn thấy điều này sẽ gây ra quá nhiều thiệt hại. Nhưng bây giờ, con trỏ thành viên cho phép phá vỡ sự bảo vệ này! Điểm mấu chốt là kiểu con trỏ thành viên được liên kết với lớp thực sự chứa thành viên đó - không phải với lớp mà bạn đã chỉ định khi lấy địa chỉ. Điều này cho phép chúng tôi tránh kiểm tra

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

Và tất nhiên, nó cũng hoạt động với std::stackví dụ.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Điều đó sẽ trở nên dễ dàng hơn với một khai báo sử dụng trong lớp dẫn xuất, khai báo này làm cho tên thành viên ở chế độ công khai và tham chiếu đến thành viên của lớp cơ sở.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

Một tính năng ẩn khác là bạn có thể gọi các đối tượng lớp có thể được chuyển đổi thành con trỏ hàm hoặc tham chiếu. Việc giải quyết quá tải được thực hiện dựa trên kết quả của chúng và các đối số được chuyển tiếp hoàn hảo.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

Chúng được gọi là "hàm gọi thay thế".


1
Khi bạn nói rằng giải quyết quá tải được thực hiện dựa trên kết quả của chúng, bạn có nghĩa là nó thực sự chuyển đổi nó sang cả Functors và sau đó thực hiện giải quyết quá tải? Tôi đã thử in một cái gì đó trong toán tử Func1 * () và toán tử Func2 * (), nhưng dường như nó chọn đúng khi nó tìm ra toán tử chuyển đổi nào cần gọi.
hoa tiêu

3
@navigator, đúng là nó chuyển đổi về mặt khái niệm cho cả hai và sau đó chọn thứ tốt nhất. Nó không cần thực sự gọi chúng, bởi vì nó biết từ loại kết quả chúng sẽ mang lại những gì. Cuộc gọi thực sự được thực hiện khi nó cuối cùng đã được chọn.
Johannes Schaub - litb

26

Các tính năng ẩn:

  1. Các chức năng ảo thuần túy có thể có triển khai. Ví dụ phổ biến, bộ hủy ảo thuần túy.
  2. Nếu một hàm ném ra một ngoại lệ không được liệt kê trong đặc tả ngoại lệ của nó, nhưng hàm có std::bad_exceptiontrong đặc tả ngoại lệ của nó, thì ngoại lệ đó sẽ được chuyển đổi thành std::bad_exceptionvà ném tự động. Bằng cách đó, ít nhất bạn sẽ biết rằng a bad_exceptionđã được ném. Đọc thêm tại đây .

  3. chức năng thử khối

  4. Từ khóa mẫu trong việc phân biệt các typedef trong một mẫu lớp. Nếu tên của chuyên ngành mẫu thành viên xuất hiện sau một ., ->hoặc ::toán tử và tên đó có các tham số mẫu đủ điều kiện rõ ràng, hãy thêm tiền tố tên mẫu thành viên với mẫu từ khóa. Đọc thêm tại đây .

  5. Mặc định tham số hàm có thể được thay đổi trong thời gian chạy. Đọc thêm tại đây .

  6. A[i] hoạt động tốt như i[A]

  7. Các phiên bản tạm thời của một lớp có thể được sửa đổi! Một hàm thành viên không phải const có thể được gọi trên một đối tượng tạm thời. Ví dụ:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    Đọc thêm tại đây .

  8. Nếu có hai kiểu khác nhau trước và sau :trong ?:biểu thức toán tử ternary ( ), thì kiểu kết quả của biểu thức là kiểu tổng quát nhất trong hai kiểu. Ví dụ:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Daddy: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Tôi nhận được dấu phẩy, chỉ là điều này có nghĩa là [] không có giá trị ngữ nghĩa của riêng nó và chỉ đơn giản là tương đương với sự thay thế kiểu macro trong đó "x [y]" được thay thế bằng "(* ((x) + (y ))) ". Không phải ở tất cả những gì tôi mong đợi. Tôi tự hỏi tại sao nó được định nghĩa theo cách này.
P Daddy

Khả năng tương thích ngược với C
jmucchiello

2
Về điểm đầu tiên của bạn: Có một trường hợp cụ thể mà bạn phải triển khai một hàm thuần ảo: hàm hủy thuần ảo.
Frerich Raabe

24

map::operator[]tạo mục nhập nếu thiếu khóa và trả về tham chiếu đến giá trị mục nhập được tạo mặc định. Vì vậy, bạn có thể viết:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

Tôi ngạc nhiên về việc có bao nhiêu lập trình viên C ++ không biết điều này.


11
Và trên hết đối diện bạn không thể sử dụng toán tử [] trên bản đồ const
David Rodríguez - dribeas

2
+1 cho Nick, mọi người có thể phát điên nếu họ không biết về .find().
LiraNuna 09/09/09

hoặc " const map::operator[]tạo thông báo lỗi"
chỉ ai đó

2
Không phải là một tính năng của ngôn ngữ, nó là một tính năng của thư viện mẫu Chuẩn. Nó cũng khá rõ ràng, vì toán tử [] trả về một tham chiếu hợp lệ.
Ramon Zarazua B.

2
Tôi đã phải sử dụng bản đồ trong C # trong một thời gian, nơi bản đồ không hoạt động theo cách đó, để nhận ra rằng đây là một đối tượng địa lý. Tôi nghĩ rằng tôi đã khó chịu vì nó nhiều hơn là tôi đã sử dụng nó, nhưng có vẻ như tôi đã sai. Tôi thiếu nó trong C #.
sbi 20/09/10

20

Việc đặt các hàm hoặc biến trong không gian tên không tên sẽ không sử dụng staticđể hạn chế chúng trong phạm vi tệp.


"phản đối" là một thuật ngữ mạnh…
Potatoswatter

@Potato: Nhận xét cũ, tôi biết, nhưng tiêu chuẩn nói rằng việc sử dụng tĩnh trong phạm vi không gian tên không được chấp nhận, với ưu tiên cho các không gian tên không được đặt tên.
GManNickG

@GMan: không có vấn đề gì, tôi không nghĩ các trang SO thực sự "chết". Đối với cả hai mặt của câu chuyện, statictrong phạm vi toàn cầu không bị phản đối theo bất kỳ cách nào. (Tham khảo: C ++ 03 §D.2)
Potatoswatter

À, đọc kỹ hơn, "Tên được khai báo trong không gian tên chung có phạm vi không gian tên toàn cầu (còn gọi là phạm vi toàn cầu)." Điều đó thực sự có nghĩa là như vậy?
Potatoswatter

@Potato: Đúng vậy. :) staticsử dụng chỉ nên được sử dụng trong một loại lớp hoặc hàm.
GManNickG

19

Việc xác định các hàm bạn bè thông thường trong các mẫu lớp cần đặc biệt chú ý:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

Trong ví dụ này, hai cách diễn đạt khác nhau tạo ra hai định nghĩa giống hệt nhau — vi phạm trực tiếp ODR

Do đó, chúng ta phải đảm bảo các tham số mẫu của mẫu lớp xuất hiện trong kiểu của bất kỳ hàm bạn bè nào được xác định trong mẫu đó (trừ khi chúng ta muốn ngăn nhiều hơn một lần khởi tạo mẫu lớp trong một tệp cụ thể, nhưng điều này khá khó xảy ra). Hãy áp dụng điều này cho một biến thể của ví dụ trước của chúng tôi:

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

Tuyên bố từ chối trách nhiệm: Tôi đã dán phần này từ C ++ Templates: The Complete Guide / Section 8.4


18

các hàm void có thể trả về giá trị void

Ít được biết đến, nhưng mã sau đây là tốt

void f() { }
void g() { return f(); }

Cũng như cái nhìn kỳ lạ sau đây

void f() { return (void)"i'm discarded"; }

Biết về điều này, bạn có thể tận dụng trong một số lĩnh vực. Một ví dụ: các voidhàm không thể trả về một giá trị nhưng bạn cũng không thể trả về không có giá trị nào, bởi vì chúng có thể được khởi tạo bằng non-void. Thay vì lưu trữ giá trị vào một biến cục bộ, điều này sẽ gây ra lỗi cho void, chỉ cần trả về một giá trị trực tiếp

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

Đọc tệp thành một vectơ chuỗi:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Hoặc: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
ý bạn là vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- thiếu dấu ngoặc đơn sau thông số thứ hai
knittl

1
Đây không thực sự là một tính năng C ++ ẩn. Thêm tính năng STL. STL! = Một ngôn ngữ
Nick Bedford

14

Bạn có thể tạo mẫu các trường bit.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Tôi vẫn chưa nghĩ ra bất kỳ mục đích nào cho việc này, nhưng chắc chắn là tôi đã rất ngạc nhiên.


1
Xem tại đây, nơi gần đây tôi đã đề xuất nó cho số học n-bit: stackoverflow.com/questions/8309538/…
sehe

14

Một trong những ngữ pháp thú vị nhất của bất kỳ ngôn ngữ lập trình nào.

Ba trong số những thứ này thuộc về nhau, và hai thứ hoàn toàn khác nhau ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Tất cả ngoại trừ SomeTypeđối tượng thứ ba và thứ năm xác định một đối tượng trên ngăn xếp và khởi tạo nó (với utrong hai trường hợp đầu tiên và phương thức khởi tạo mặc định ở thứ tư. Câu thứ ba là khai báo một hàm không nhận tham số và trả về a SomeType. Thứ năm cũng khai báo tương tự một hàm nhận một tham số theo giá trị của kiểu SomeTypeđược đặt tên u.


có sự khác biệt nào giữa thứ nhất và thứ hai không? mặc dù vậy, tôi biết cả hai đều là khởi tạo.
Özgür

Comptrol: Tôi không nghĩ vậy. Cả hai sẽ kết thúc việc gọi hàm tạo bản sao, mặc dù phương thức đầu tiên LOOKS giống như toán tử gán, nó thực sự là phương thức tạo bản sao.
abelenky

1
Nếu u là một kiểu khác với SomeType, thì cái đầu tiên sẽ gọi hàm tạo chuyển đổi trước rồi đến hàm tạo sao chép, trong khi cái thứ hai sẽ chỉ gọi hàm tạo chuyển đổi.
Nhật thực

3
Thứ nhất là lời gọi ngầm định của hàm tạo, thứ hai là lời gọi rõ ràng. Nhìn vào đoạn mã sau để thấy sự khác biệt: #include <iostream> class sss {public: explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // gọi hàm tạo int sss xxx = 7; // gọi hàm tạo kép return 0; }
Kirill V. Lyadvinsky

Đúng - dòng đầu tiên sẽ không hoạt động nếu hàm tạo được khai báo rõ ràng.
Nhật thực

12

Loại bỏ các khai báo chuyển tiếp:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

Viết câu lệnh switch với?: Toán tử:

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Làm mọi thứ trên một dòng:

void a();
int b();
float c = (a(),b(),1.0f);

Zeroing cấu trúc không có memset:

FStruct s = {0};

Chuẩn hóa / giá trị góc - và thời gian gói:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Gán tài liệu tham khảo:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};thậm chí còn ngắn hơn.
Constantin

Trong ví dụ cuối cùng, nó sẽ đơn giản hơn với: a (); b (); float c = 1,0f;
Zifre

2
Cú pháp này "float c = (a (), b (), 1.0f);" rất hữu ích để làm nổi bật phép toán gán (gán "c"). Phép toán gán rất quan trọng trong lập trình vì chúng ít có khả năng trở thành IMO không dùng nữa. Không biết tại sao, có thể là điều gì đó liên quan đến lập trình chức năng trong đó trạng thái chương trình được gán lại cho mọi khung. Tái bút. Và không, "int d = (11,22,1.0f)" sẽ bằng "1". Đã thử nghiệm một phút trước với VS2008.
AareP

2
+1 Bạn không nên gọi main ? Tôi muốn đề xuất global().main();và chỉ cần quên về singleton ( bạn chỉ có thể làm việc với cái tạm thời, điều này sẽ kéo dài thời gian tồn tại của nó )
xem

1
Tôi nghi ngờ việc chỉ định tài liệu tham khảo là di động. Tôi thích cấu trúc để từ bỏ các khai báo chuyển tiếp.
Thomas Eding

12

Toán tử điều kiện bậc ba ?:yêu cầu toán hạng thứ hai và thứ ba của nó phải có kiểu "dễ chấp nhận" (nói một cách không chính thức). Nhưng yêu cầu này có một ngoại lệ (dự định chơi chữ): toán hạng thứ hai hoặc thứ ba có thể là một biểu thức ném (có kiểuvoid ), bất kể kiểu của toán hạng khác.

Nói cách khác, người ta có thể viết các biểu thức C ++ hợp lệ không chính xác sau đây bằng cách sử dụng ?:toán tử

i = a > b ? a : throw something();

BTW, thực tế là biểu thức ném thực sự là một biểu thức (kiểu void) chứ không phải là một câu lệnh là một tính năng ít được biết đến của ngôn ngữ C ++. Điều này có nghĩa là, trong số những thứ khác, mã sau hoàn toàn hợp lệ

void foo()
{
  return throw something();
}

mặc dù không có nhiều điểm khi làm theo cách này (có thể trong một số mã mẫu chung, điều này có thể hữu ích).


Đối với những gì nó đáng giá, Neil có một câu hỏi về điều này: stackoverflow.com/questions/1212978/… , chỉ để biết thêm thông tin.
GManNickG

12

Quy tắc thống trị rất hữu ích, nhưng ít được biết đến. Nó nói rằng ngay cả khi trong một đường dẫn không phải là duy nhất qua mạng lớp cơ sở, việc tra cứu tên cho một thành viên bị ẩn một phần là duy nhất nếu thành viên đó thuộc về một lớp cơ sở ảo:

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

Tôi đã sử dụng điều này để triển khai hỗ trợ căn chỉnh tự động tìm ra sự liên kết chặt chẽ nhất bằng quy tắc thống trị.

Điều này không chỉ áp dụng cho các hàm ảo mà còn cho các tên typedef, các thành viên tĩnh / không ảo và bất cứ thứ gì khác. Tôi đã thấy nó được sử dụng để triển khai các đặc điểm ghi đè trong các chương trình meta.


1
Khéo léo. Bất kỳ lý do cụ thể nào mà bạn đưa struct Cvào ví dụ của mình ...? Chúc mừng.
Tony Delroy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.