it-swarm-vi.tech

Các tính năng ẩn của C ++?

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

114
Craig H

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

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 giá trị:

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

đó là tốc ký cho

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

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

308
Ferruccio

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

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

    ...
}
238
Ben

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

C++ thú vị nhất tôi từng thấy qua? Tương tự bằng chữ. .

140
Anonymouse

Tôi đồng ý với hầu hết các bài đăng ở đó: C++ là ngôn ngữ đa mô hình, do đó, các tính năng "ẩn" bạn sẽ tìm thấy (ngoài "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ác phương tiện.

Hầu hết các cơ sở đó 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.

Điều quan trọng nhất là [~ # ~] raii [~ # ~] , thường bị bỏ qua trong nhiều năm bởi các nhà phát triển C++ đến từ thế giới C. Quá tải 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 như mảng (toán tử đăng ký), con trỏ như các hoạt động (con trỏ thông minh) và giống như xây dựng hoạt độ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 mã thực sự mạnh mẽ thông qua an toàn ngoại lệ thông số kỹ thuật (bao gồm mã 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 trở lại trạng thái ban đầu).

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

Những cách 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++, nghĩa là, C.

Bằng cách sử dụng functor , bạn có thể mô phỏng các chức năng, với loại an toàn bổ sung và có trạng thái. Sử dụng mẫu mẫu , bạn có thể trì hoãn 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 "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 mã sẽ hoạt động trên hầu hết các loại, bao gồm cả loại mà bạn nghĩ lúc đầu. Bạn cũng có thể tăng độ an toàn của loại (như kiểu malloc/realloc/miễn phí tự động). Các tính năng đối tượng C++ thực sự mạnh mẽ (và do đó, nguy hiểm nếu được sử dụng một cách bất cẩn), nhưng ngay cả đa hình động cũng có phiên bản tĩnh trong C++: [~ # ~] crtp [~ # ~] .

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

Trong số các ưu tiên của tôi là một thứ nên làm cho tóc của bất kỳ Java nổi lên từ kinh dị: Trong C++, cách hướng đối tượng nhất để thêm một tính năng vào một đối tượng thông qua hàm không phải là thành viên, thay vì hàm thành viên (tức là phương thức lớp), bởi vì:

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

  • các hàm không phải thành viên không phải là thành viên không có quyền truy cập đặc quyền vào lớp bên trong. Như vậy, sử dụng hàm thành viên trên một người không phải là bạn bè sẽ làm suy yếu sự đóng gói của lớp.

Điều này không bao giờ thất bại để gây ngạc nhiên ngay cả các nhà phát triển có kinh nghiệm.

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

119
paercebal

Một tính năng ngôn ngữ mà tôi cho là hơi bị ẩn, bởi vì tôi chưa bao giờ nghe về nó trong suốt thời gian ở trường, đó là bí danh không gian tên. Nó đã không được tôi chú ý cho đến khi tôi bắt gặp 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++ tiêu chuẩn nào.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );
118
Jason Mock

Các biến không chỉ có thể được khai báo trong phần init của vòng lặp for, mà 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 số của các loại khác nhau.

102

Toán tử mảng là kết hợp.

A [8] là từ đồng nghĩa với * (A + 8). Vì phép cộng là liên kết, 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 ... :-)

77
Colin Jensen

Một điều ít được biết đến là các công đoàn cũng có thể là 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à họ có thể có các hàm tạo và các hàm thành viên. Không có gì phải làm với sự kế thừa (bao gồm các hàm ảo).

73

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ố rất nhiều: mẫu siêu lập trình . Không ai trong ủy ban tiêu chuẩn dự định có một ngôn ngữ con hoàn chỉnh Turing được thực thi tại thời gian biên dịch.

72
Konrad Rudolph

Một tính năng ẩn khác không hoạt động trong C là chức năng của unary + nhà điều hành. Bạn có thể sử dụng nó để thúc đẩy và phân rã tất cả các loại

Chuyển đổi một liệt kê thành một số nguyên

+AnEnumeratorValue

Và giá trị liệt kê của bạn trước đây có loại liệt kê của nó bây giờ có loại số nguyên hoàn hảo có thể phù hợp với giá trị của nó. Thủ công, bạn sẽ khó có thể biết loại đó! Điều này là cần thiết, ví dụ khi bạn muốn triển khai một toán tử quá tải cho phép 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 trình 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? Toán tử có thể giúp tạo tạm thời mà không cần 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ỏ tới một hàm, nhưng nó sẽ không hoạt độ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
}
66

Thời gian tồn tại của thời gian bị ràng buộc với các tham chiếu const là một đ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 không biết đến.

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

Một tính năng hay không được sử dụng thường xuyên là khối thử bắt toàn chức năng:

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

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

52
vividos

Nhiều người biết về siêu dữ liệu identity/id, nhưng có một usecase Nice cho nó cho các trường hợp không phải mẫu: Dễ viết khai báo:

// 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; };
44

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

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

Một số macro sử dụng, ví dụ để cung cấp một số phạm vi "bị khóa" như thế này:

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 điều này, không chỉ có thể trong một, mà còn trong một công tắc:

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

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

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

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

43
Johannes Schaub - litb

Ngăn chặn toán tử dấu phẩy gọi quá tải toán tử

Đôi khi bạn sử dụng toán tử dấu phẩy hợp lệ, nhưng bạn muốn đảm bảo rằng không có toán tử dấu phẩy do người dùng xác định nào xâm nhập, vì ví dụ, bạn dựa vào các điểm chuỗi 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ở mong muốn hoạt động. Đây là nơi void() đi vào trò chơi:

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

Bỏ qua các chủ sở hữu nơi tôi đặt cho các điều kiện và mã. Điều quan trọng là void(), làm cho trình biên dịch buộc phải sử dụng toán tử dấu phẩy dựng sẵn. Điều này có thể hữu ích khi thực hiện các lớp đặc điểm, đôi khi, quá.

29

Khởi tạo mảng trong constructor. Ví dụ: trong một lớp nếu chúng ta có một mảng int là:

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 thành 0) trong hàm tạo như:

clName::clName() : a()
{
}
28
Poorna

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

  • Các cấu trúc cần phải là ả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
  • Các cụm cục bộ không thể được sử dụng làm tham số mẫu (làm cho chúng ít hữu ích hơn)
  • specifier 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óa hữu ích nào về quốc tế hóa (bộ ký tự rộng tiêu chuẩn di động, có ai không? Chúng ta sẽ phải đợi đến 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 sử dụng cho nó. Vâng, tôi biết lý do tại sao họ thêm nó, nhưng bạn phải suy nghĩ lại về một hàm tạo khiến nó trở nên vô nghĩa.
  • Thật đáng để xem xét cẩn thận các đảm bảo STL về tính hợp lệ của trình vòng 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.
  • Tăng cường - hầu như không phải là một bí mật nhưng nó đáng để sử dụng.
  • Tối ưu hóa giá trị trả về (không rõ ràng, nhưng được tiêu chuẩn cho phép cụ thể)
  • Functor aka hàm đối tượng aka toán tử (). Đ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 quá tải toán tử và các mẫu.
27
Robert

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, mà không có hành vi không xác định và với ngữ nghĩa dự kiến. Đọc để xem làm thế nào. Đọc thêm báo cáo lỗi về điều này.

Thông thường, C++ cấm bạn truy cập 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 điều gì. Nó có thể là một đối tượng C, trong trường hợp đó lớp B không có doanh nghiệp và đầu mối về dữ liệu của nó. Quyền truy cập như vậy chỉ được cấp nếu x là 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 ra một lớp "vứt đi" để đọc 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à loại con trỏ thành viên được liên kết với lớp thực sự chứa thành viên đã nói - chứ không phải với lớp mà bạn đã chỉ định khi lấy địa chỉ nhà. Điều này cho phép chúng tôi phá vỡ 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::stack thí 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ẽ còn dễ dàng hơn nữa với việc sử dụng khai báo trong lớp dẫn xuất, làm cho tên thành viên được công khai và đề cập đế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);
}
27

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

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

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

  4. Từ khóa mẫu trong định hướng typedefs trong một mẫu lớp. Nếu tên của chuyên môn mẫu thành viên xuất hiện sau một ., ->, hoặc là :: toán tử và tên đó có các tham số mẫu đủ điều kiện rõ ràng, 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ố chức năng 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. Trường hợp tạm thời của một lớp có thể được sửa đổi! Một hàm không phải là thành viên 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 hai loại khác nhau có mặt trước và sau : trong ternary (?:) biểu thức toán tử, sau đó kiểu kết quả của biểu thức là kiểu biểu thức tổng quát nhất của hai biểu thức. 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)
    }
    
26
Sumant

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 các con trỏ hàm hoặc tham chiếu. Quá trình 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à "chức năng gọi thay thế".

26

map::operator[] tạo mục nhập nếu khóa bị thiếu và trả về tham chiếu đến giá trị mục nhập được xây dựng 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 khi có nhiều lập trình viên C++ không biết điều này.

24
Constantin

Đặt các hàm hoặc biến trong một 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.

20
Jim Hunziker

Xác định các chức năng 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ảnh báo 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 loại 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 chặn nhiều hơn một lần tạo mẫu của 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ố miễn trừ trách nhiệm: Tôi đã dán phần này từ Mẫu C++: Hướng dẫn hoàn chỉnh /Mục 8.4

19
Özgür

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

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

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

Cũng như một người trông 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 lợi thế trong một số lĩnh vực. Một ví dụ: void các hàm không thể trả về một giá trị nhưng bạn cũng không thể trả về bất cứ thứ gì, vì chúng có thể được khởi tạo với giá trị không trống. 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; !
};
18

Đọc một tập tin thành một vectơ của chuỗi:

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

iux_iterator

17
Jason Baker

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 là một thứ hoàn toàn khác ...

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

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

14
Eclipse

Bạn có thể tạo mẫu bitfield.

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

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

14
Kaz Dragon

Quy tắc thống trị là 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 duy nhất thông qua một mạng lớp cơ sở, việc tìm kiếm 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 chức năng ảo mà còn áp dụng cho tên typedef, thành viên tĩnh/không ảo và bất cứ điều gì khác. Tôi đã thấy nó được sử dụng thực hiện các đặc điểm ghi đè trong các chương trình meta.

12

Toán tử điều kiện ternary ?: yêu cầu toán hạng thứ hai và thứ ba của nó phải có kiểu "dễ chịu" (nói không chính thức). Nhưng yêu cầu này có một ngoại lệ (ý định chơi chữ): toán hạng thứ hai hoặc thứ ba có thể là biểu thức ném (có loại void), bất kể loại 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ệ sau đây bằng cách sử dụng ?: nhà điều hành

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 (thuộc loại void) và không phải là một câu lệnh là một tính năng ít được biết đến khác của ngôn ngữ C++. Điều này có nghĩa, trong số những thứ khác, mã sau đây là hoàn toàn hợp lệ

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

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

12
AnT

Loại bỏ các tuyên bố chuyển tiếp:

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

Viết câu lệnh chuyển đổi 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 duy nhất:

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

Cấu trúc không giới hạn mà không cần bộ nhớ:

FStruct s = {0};

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

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

Chỉ định 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;
12
AareP

Tôi thấy blog này là một tài nguyên tuyệt vời về arcanes của C++: C++ Truths .

9
Drealmer

Một bí mật nguy hiểm là

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

Bí mật yêu thích của tôi, tôi hiếm khi thấy được sử dụng:

class A
{
};

struct B
{
  A a;
  operator A&() { return a; }
};

void func(A a) { }

int main()
{
  A a, c;
  B b;
  a=c;
  func(b); //yeah baby
  a=b; //gotta love this
}
8
user34537

Các lớp học địa phương là tuyệt vời:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

khá gọn gàng, vì nó không gây ô nhiễm không gian tên với các định nghĩa lớp vô dụng ...

8
Alexandre C.

Một tính năng ẩn, thậm chí được ẩn đối với nhà phát triển GCC , là khởi tạo một thành viên mảng bằng cách sử dụng một chuỗi ký tự. Giả sử bạn có cấu trúc cần làm việc với mảng C và bạn muốn khởi tạo thành viên mảng với nội dung mặc định

struct Person {
  char name[255];
  Person():name("???") { }
};

Điều này hoạt động, và chỉ hoạt động với mảng char và khởi tạo chuỗi ký tự. Không cần strcpy!

7

Một ví dụ trong số rất nhiều: siêu lập trình mẫu. Không ai trong ủy ban tiêu chuẩn dự định có một ngôn ngữ con hoàn chỉnh Turing được thực thi tại thời gian biên dịch.

Mẫu siêu lập trình hầu như không phải là một tính năng ẩn. Nó thậm chí còn trong thư viện boost. Xem MPL . Nhưng nếu "gần như bị ẩn" là đủ tốt, thì hãy xem thư viện boost . Nó chứa nhiều goodies không dễ dàng có được mà không có sự hỗ trợ của một thư viện mạnh.

Một ví dụ là boost.lambda thư viện, điều này rất thú vị vì C++ không có chức năng lambda trong tiêu chuẩn hiện tại.

Một ví dụ khác là Loki , trong đó "sử dụng rộng rãi siêu dữ liệu mẫu C++ và thực hiện một số công cụ thường được sử dụng: typelist, functor, singleton, con trỏ thông minh, nhà máy đối tượng, khách truy cập và multimethod." [ Wikipedia ]

6
Markowitch

Không có tính năng ẩn, nhưng ngôn ngữ C++ rất mạnh mẽ và thường thì ngay cả các nhà phát triển tiêu chuẩn cũng không thể tưởng tượng được C++ có thể được sử dụng để làm gì.

Trên thực tế từ việc xây dựng ngôn ngữ đủ đơn giản, bạn có thể viết một cái gì đó rất mạnh mẽ. Rất nhiều thứ như vậy có sẵn tại www.boost.org như một ví dụ (và http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html trong số đó ).

Để hiểu cách kết hợp ngôn ngữ đơn giản có thể được kết hợp với một thứ gì đó mạnh mẽ, thật tốt khi đọc "Mẫu C++: Hướng dẫn đầy đủ" của David Vandevoorde, Nicolai M. Josuttis và cuốn sách ma thuật thực sự "Thiết kế C++ hiện đại ..." của Andrei Alexandresc .

Và cuối cùng, rất khó để học C++, bạn nên cố gắng điền nó;)

5
sergtk

Dường như với tôi chỉ có rất ít người biết về không gian tên không tên:

namespace {
  // Classes, functions, and objects here.
}

Các không gian tên không tên hoạt động như thể chúng được thay thế bởi:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

".. trong đó tất cả các lần xuất hiện của [tên duy nhất này] trong một đơn vị dịch được thay thế bằng cùng một mã định danh và mã định danh này khác với tất cả các mã định danh khác trong toàn bộ chương trình." [C++ 03, 7.3.1.1/1]

4
vobject
4
Özgür

Tôi không chắc chắn về ẩn, nhưng có một số thú vị'thủ thuật' có lẽ không rõ ràng khi chỉ đọc thông số kỹ thuật.

3
dbrien

Có rất nhiều "hành vi không xác định". Bạn có thể học cách tránh họ đọc những cuốn sách hay và đọc các tiêu chuẩn.

3
ugasoft

Hầu hết các nhà phát triển C++ bỏ qua sức mạnh của siêu lập trình mẫu. Hãy xem Loki Libary . Nó triển khai một số công cụ nâng cao như typelist, functor, singleton, con trỏ thông minh, nhà máy đối tượng, khách truy cập và multimethods sử dụng rộng rãi mẫu siêu lập trình (từ wikipedia ). Đối với hầu hết các phần, bạn có thể coi đây là tính năng "ẩn" c ++.

3
Sridhar Iyer

Từ Sự thật C++ .

Xác định các hàm có chữ ký giống hệt nhau trong cùng phạm vi, vì vậy đây là hợp pháp:

template<class T> // (a) a base template
void f(T) {
  std::cout << "f(T)\n";
}

template<>
void f<>(int*) { // (b) an explicit specialization
  std::cout << "f(int *) specilization\n";
}

template<class T> // (c) another, overloads (a)
void f(T*) {
  std::cout << "f(T *)\n";
}

template<>
void f<>(int*) { // (d) another identical explicit specialization
  std::cout << "f(int *) another specilization\n";
}
3
Özgür
  • con trỏ đến phương thức lớp
  • Từ khóa "typename"
3
shoosh
3
sdcvvc

main () không cần giá trị trả về:

int main(){}

là chương trình C++ hợp lệ ngắn nhất.

2
Jeffrey Faust

Chú ý đến sự khác biệt giữa con trỏ hàm miễn phí và khởi tạo con trỏ hàm thành viên:

chức năng thành viên:

struct S
{
 void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

và chức năng miễn phí:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well; 
}

Nhờ vào phần thừa này &, bạn có thể thêm các trình điều khiển luồng - là các hàm miễn phí - trong chuỗi mà không có nó:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
2
Özgür
  1. map::insert(std::pair(key, value)); không ghi đè nếu giá trị khóa đã tồn tại.

  2. Bạn có thể khởi tạo một lớp ngay sau định nghĩa của nó: (Tôi có thể thêm rằng tính năng này đã gây ra cho tôi hàng trăm lỗi biên dịch do dấu chấm phẩy bị thiếu và tôi chưa từng thấy ai sử dụng tính năng này trên các lớp)

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

Có hàng tấn cấu trúc "khó" trong C++. Họ đi từ các triển khai "đơn giản" của các lớp niêm phong/cuối cùng bằng cách sử dụng kế thừa ảo. Và nhận được các cấu trúc lập trình meta khá "phức tạp" như Boost's MPL ( hướng dẫn ). Khả năng tự bắn vào chân là vô tận, nhưng nếu được kiểm tra (tức là các lập trình viên dày dạn), cung cấp một số tính linh hoạt tốt nhất về khả năng bảo trì và hiệu suất.

1
Amir

Các lớp và các lớp lớp struct gần giống nhau. Sự khác biệt chính là các lớp mặc định truy cập riêng tư cho các thành viên và cơ sở, trong khi cấu trúc mặc định thành công khai:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };

// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };

// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

Các hiệp hội cũng có thể có các thành viên và phương thức, và mặc định truy cập công khai tương tự như các cấu trúc.

1
a_m0d

Thành ngữ chuyển đổi gián tiếp :

Giả sử bạn đang thiết kế một lớp con trỏ thông minh. Ngoài việc quá tải các toán tử * và ->, một lớp con trỏ thông minh thường định nghĩa một toán tử chuyển đổi thành bool:

template <class T>
class Ptr
{
public:
 operator bool() const
 {
  return (rawptr ? true: false);
 }
//..more stuff
private:
 T * rawptr;
};

Việc chuyển đổi thành bool cho phép khách hàng sử dụng các con trỏ thông minh trong các biểu thức yêu cầu toán hạng bool:

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
 cout<<"int value is: "<<*ptr <<endl;
else
 cout<<"empty"<<endl;

Hơn nữa, việc chuyển đổi ngầm định thành bool được yêu cầu trong các khai báo có điều kiện như:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
 //we get here only of px isn't empty
} 

Than ôi, chuyển đổi tự động này mở ra cánh cổng cho những bất ngờ không mong muốn:

Ptr <int> p1;
Ptr <double> p2;

//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl; 
//prints 0, 1, or 2, although there isn't an overloaded operator+()

Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated 

//surprise #2
if(pf==pq) //compares bool values, not pointers! 

Giải pháp: Sử dụng thành ngữ "chuyển đổi gián tiếp", bằng cách chuyển đổi từ con trỏ sang thành viên dữ liệu [pMember] thành bool để sẽ chỉ có 1 chuyển đổi ngầm định, điều này sẽ ngăn chặn hành vi bất ngờ đã nói ở trên: pMember-> bool thay vì bool-> một cái gì đó khác.

1
Özgür

Nếu toán tử xóa () lấy đối số kích thước ngoài * void, điều đó có nghĩa là, rất, nó sẽ là một lớp cơ sở. Đối số kích thước đó có thể kiểm tra kích thước của các loại để hủy chính xác. Đây là những gì Stephen Dewhurst nói về điều này:

Cũng lưu ý rằng chúng tôi đã sử dụng một phiên bản hai đối số của toán tử xóa thay vì phiên bản một đối số thông thường. Phiên bản hai đối số này là một phiên bản "thông thường" khác của toán tử thành viên thường được sử dụng bởi các lớp cơ sở mong muốn các lớp dẫn xuất kế thừa việc thực hiện xóa toán tử của chúng. Đối số thứ hai sẽ chứa kích thước của đối tượng bị xóa thông tin của bộ phận dữ liệu thường hữu ích trong việc thực hiện quản lý bộ nhớ tùy chỉnh.

1
Özgür

Tôi thấy bản năng mẫu đệ quy khá tuyệt:

template<class int>
class foo;

template
class foo<0> {
    int* get<0>() { return array; }
    int* array;  
};

template<class int>
class foo<i> : public foo<i-1> {
    int* get<i>() { return array + 1; }  
};

Tôi đã sử dụng điều đó để tạo ra một lớp có 10-15 hàm trả về các con trỏ vào các phần khác nhau của một mảng, vì một API tôi đã sử dụng một con trỏ hàm cho mỗi giá trị.

I E. lập trình trình biên dịch để tạo ra một loạt các hàm, thông qua đệ quy. Dễ như ăn bánh. :)

1
Macke

Yêu thích của tôi (trong thời điểm hiện tại) là thiếu sơ đồ trong một tuyên bố như A = B = C. Giá trị của A về cơ bản là không xác định.

Hãy nghĩ về điều này:

class clC
{
public:
   clC& operator=(const clC& other)
   {
      //do some assignment stuff
      return copy(other);
   }
   virtual clC& copy(const clC& other);
}

class clB : public clC
{
public:
  clB() : m_copy()
  {
  }

  clC& copy(const clC& other)
  {
    return m_copy;
  }

private:
  class clInnerB : public clC
  {
  }
  clInnerB m_copy;
}

bây giờ A có thể thuộc loại không thể truy cập được với bất kỳ đối tượng nào khác ngoài các đối tượng thuộc loại clB và có giá trị không liên quan đến C.

0
Rune FS

Thêm ràng buộc vào mẫu.

0
Özgür

Con trỏ thành viên và toán tử con trỏ thành viên -> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
    A* a = new A();
    a->d = 8;
    printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
    return 0;
}

Đối với các phương thức (a -> * & A :: e) () hơi giống với Function.call () từ javascript

var f = A.e
f.call(a) 

Đối với các thành viên, nó giống như truy cập với toán tử []

a['d']
0
Kamil Szot

Bạn có thể xem tất cả các macro được xác định trước thông qua các chuyển đổi dòng lệnh với một số trình biên dịch. Điều này hoạt động với gcc và icc (trình biên dịch C++ của Intel):

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

Đối với MSVC, chúng được liệt kê ở vị trí đơn . Chúng có thể được ghi lại ở một nơi duy nhất cho những người khác, nhưng với các lệnh trên, bạn có thể thấy rõ xem những gì được và không được xác định và chính xác là gì các giá trị được sử dụng, sau khi áp dụng tất cả các công tắc dòng lệnh khác.

So sánh (sau khi sắp xếp):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt
0
Roger Pate
class Empty {};

namespace std {
  // #1 specializing from std namespace is okay under certain circumstances
  template<>
  void swap<Empty>(Empty&, Empty&) {} 
}

/* #2 The following function has no arguments. 
   There is no 'unknown argument list' as we do
   in C.
*/
void my_function() { 
  cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
  // and this doesn't affect things outside of that scope
}

int main() {
  using namespace std; /* #4 you can use using in function scopes */
  cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
  /* #6 falling off of main without an explicit return means "return 0;" */
}
0
dirkgently