Tại sao tôi có thể xác định cấu trúc và lớp trong một hàm trong C ++?


90

Tôi chỉ làm nhầm một cái gì đó như thế này trong C ++, và nó hoạt động. Tại sao tôi có thể làm điều này?

int main(int argc, char** argv) {
    struct MyStruct
    {
      int somevalue;
    };

    MyStruct s;
    s.somevalue = 5;
}

Bây giờ sau khi làm điều này, tôi nhớ là đã đọc về thủ thuật này ở một nơi nào đó, cách đây rất lâu, như một loại công cụ lập trình chức năng của người nghèo dành cho C ++, nhưng tôi không thể nhớ tại sao nó lại hợp lệ hoặc tôi đã đọc nó ở đâu.

Câu trả lời cho một trong hai câu hỏi đều được chào đón!

Lưu ý: Mặc dù khi viết câu hỏi, tôi không nhận được bất kỳ tài liệu tham khảo nào cho câu hỏi này , thanh bên hiện tại chỉ ra điều đó nên tôi sẽ đưa nó vào đây để tham khảo, dù câu hỏi khác nhau nhưng có thể hữu ích.


Câu trả lời:


70

[EDIT 18/4/2013]: Rất vui, hạn chế được đề cập bên dưới đã được dỡ bỏ trong C ++ 11, vì vậy sau cùng thì các lớp được định nghĩa cục bộ rất hữu ích! Cảm ơn người bình luận bamboon.

Khả năng xác định cục bộ các lớp sẽ làm cho việc tạo các bộ operator()()chức năng tùy chỉnh (các lớp có hàm so sánh, ví dụ: để chuyển tới std::sort()hoặc "các thân vòng lặp" được sử dụng std::for_each()) thuận tiện hơn nhiều.

Thật không may, C ++ cấm sử dụng các lớp được xác định cục bộ với các mẫu , vì chúng không có liên kết. Vì hầu hết các ứng dụng của functor liên quan đến các kiểu mẫu được tạo khuôn mẫu trên kiểu functor, không thể sử dụng các lớp được xác định cục bộ cho việc này - bạn phải xác định chúng bên ngoài hàm. :(

[CHỈNH SỬA 1/11/2009]

Trích dẫn có liên quan từ tiêu chuẩn là:

14.3.1 / 2: .Một kiểu cục bộ, kiểu không có liên kết, kiểu không có tên hoặc kiểu kết hợp từ bất kỳ kiểu nào trong số này sẽ không được sử dụng làm đối số mẫu cho tham số kiểu mẫu.


2
Mặc dù theo kinh nghiệm, điều này dường như hoạt động với MSVC ++ 8. (Nhưng không phải với g ++.)
j_random_hacker

Tôi đang sử dụng gcc 4.3.3 và nó có vẻ hoạt động ở đó: pastebin.com/f65b876b . Bạn có tham khảo nơi tiêu chuẩn cấm không? Đối với tôi, dường như nó có thể dễ dàng được khởi tạo tại thời điểm sử dụng.
Catskul

@Catskul: 14.3.1 / 2: "Kiểu cục bộ, kiểu không có liên kết, kiểu không có tên hoặc kiểu được kết hợp từ bất kỳ kiểu nào trong số này sẽ không được sử dụng làm đối số mẫu cho tham số kiểu mẫu". Tôi đoán cơ sở lý luận là các lớp cục bộ sẽ yêu cầu thêm một loạt thông tin khác được đưa vào các tên bị xáo trộn, nhưng tôi không biết chắc điều đó. Tất nhiên, một trình biên dịch cụ thể có thể cung cấp các phần mở rộng để giải quyết vấn đề này, vì có vẻ như MSVC ++ 8 và các phiên bản g ++ gần đây làm được.
j_random_hacker

9
Hạn chế này đã được dỡ bỏ trong C ++ 11.
Stephan Dollberg

31

Một ứng dụng của các lớp C ++ được xác định cục bộ nằm trong mẫu thiết kế Factory :


// In some header
class Base
{
public:
    virtual ~Base() {}
    virtual void DoStuff() = 0;
};

Base* CreateBase( const Param& );

// in some .cpp file
Base* CreateBase( const Params& p )
{
    struct Impl: Base
    {
        virtual void DoStuff() { ... }
    };

    ...
    return new Impl;
}

Mặc dù bạn có thể làm tương tự với không gian tên ẩn danh.


Hấp dẫn! Mặc dù các hạn chế liên quan đến các mẫu mà tôi đề cập sẽ được áp dụng, nhưng cách tiếp cận này đảm bảo rằng các phiên bản của Impl không thể được tạo (hoặc thậm chí được nói đến!) Ngoại trừ CreateBase (). Vì vậy, đây có vẻ là một cách tuyệt vời để giảm mức độ mà khách hàng phụ thuộc vào chi tiết triển khai. +1.
j_random_hacker

26
Đó là một ý tưởng gọn gàng, không chắc chắn nếu tôi sẽ sử dụng nó bất cứ lúc nào sớm, nhưng có lẽ một tốt nhất để kéo ra tại quầy bar để gây ấn tượng với một số gà con :)
Robert Gould

2
(Tôi đang nói về những chú gà con BTW, không phải câu trả lời!)
markh44

9
lol Robert ... Vâng, không có gì khá ấn tượng một người phụ nữ như biết về góc tối nghĩa của C ++ ...
j_random_hacker

10

Nó thực sự rất hữu ích để thực hiện một số công việc an toàn ngoại lệ dựa trên ngăn xếp. Hoặc dọn dẹp chung từ một chức năng có nhiều điểm trả về. Đây thường được gọi là thành ngữ RAII (thu nhận tài nguyên là khởi tạo).

void function()
{

    struct Cleaner
    {
        Cleaner()
        {
            // do some initialization code in here
            // maybe start some transaction, or acquire a mutex or something
        }

        ~Cleaner()
        {
             // do the associated cleanup
             // (commit your transaction, release your mutex, etc.)
        }
    };

    Cleaner cleaner;

    // Now do something really dangerous
    // But you know that even in the case of an uncaught exception, 
    // ~Cleaner will be called.

    // Or alternatively, write some ill-advised code with multiple return points here.
    // No matter where you return from the function ~Cleaner will be called.
}

5
Cleaner cleaner();Tôi nghĩ đây sẽ là khai báo hàm chứ không phải là định nghĩa đối tượng.
người dùng

2
@ người dùng Bạn đúng. Để gọi hàm tạo mặc định, anh ta nên viết Cleaner cleaner;hoặc Cleaner cleaner{};.
callyalater

Các lớp bên trong các hàm không liên quan gì đến RAII và ngoài ra, đây không phải là mã C ++ hợp lệ và sẽ không biên dịch.
Mikhail Vasilyev

1
Ngay cả bên trong các hàm, các lớp như thế này là CHÍNH XÁC những gì RAII trong C ++ là tất cả.
Christopher Bruns,

9

Về cơ bản, tại sao không? A structtrong C (quay trở lại bình minh của thời gian) chỉ là một cách để khai báo cấu trúc bản ghi. Nếu bạn muốn một biến, tại sao không thể khai báo nó mà bạn sẽ khai báo một biến đơn giản?

Một khi bạn làm điều đó, hãy nhớ rằng mục tiêu của C ++ là tương thích với C nếu có thể. Vì vậy, nó đã ở lại.


loại một tính năng gọn gàng đã tồn tại, nhưng như j_random_hacker vừa chỉ ra rằng nó không hữu ích như tôi đã tưởng tượng trong C ++: /
Robert Gould

Yeah, các quy tắc xác định phạm vi cũng kỳ lạ trong C. Tôi nghĩ, bây giờ tôi đã có hơn 25 năm kinh nghiệm với C ++, rằng việc phấn đấu để trở nên giống C như họ đã làm có thể là một sai lầm. Mặt khác, những ngôn ngữ thanh lịch hơn như Eiffel gần như không được áp dụng dễ dàng.
Charlie Martin

Có, tôi đã di chuyển cơ sở mã C hiện có sang C ++ (nhưng không sang Eiffel).
ChrisW


3

Nó để tạo các mảng đối tượng được khởi tạo đúng cách.

Tôi có một lớp C không có hàm tạo mặc định. Tôi muốn một mảng các đối tượng thuộc lớp C. Tôi tìm ra cách tôi muốn các đối tượng đó được khởi tạo, sau đó dẫn xuất một lớp D từ C bằng một phương thức tĩnh cung cấp đối số cho hàm tạo mặc định của C trong D:

#include <iostream>
using namespace std;

class C {
public:
  C(int x) : mData(x)  {}
  int method() { return mData; }
  // ...
private:
  int mData;
};

void f() {

  // Here I am in f.  I need an array of 50 C objects starting with C(22)

  class D : public C {
  public:
    D() : C(D::clicker()) {}
  private:
    // I want my C objects to be initialized with consecutive
    // integers, starting at 22.
    static int clicker() { 
      static int current = 22;
      return current++;
    } 
  };

  D array[50] ;

  // Now I will display the object in position 11 to verify it got initialized
  // with the right value.  

  cout << "This should be 33: --> " << array[11].method() << endl;

  cout << "sizodf(C): " << sizeof(C) << endl;
  cout << "sizeof(D): " << sizeof(D) << endl;

  return;

}

int main(int, char **) {
  f();
  return 0;
}

Để đơn giản, ví dụ này sử dụng một hàm tạo không mặc định tầm thường và một trường hợp mà các giá trị được biết tại thời điểm biên dịch. Thật đơn giản khi mở rộng kỹ thuật này cho các trường hợp bạn muốn một mảng các đối tượng được khởi tạo với các giá trị chỉ được biết trong thời gian chạy.


Chắc chắn là một ứng dụng thú vị! Mặc dù vậy, không chắc nó là khôn ngoan hay thậm chí an toàn - nếu bạn cần coi mảng D đó là mảng C (ví dụ bạn cần chuyển nó cho một hàm nhận D*tham số) thì điều này sẽ âm thầm phá vỡ nếu D thực sự lớn hơn C . (Tôi nghĩ ...)
j_random_hacker

+ j_random_hacker, sizeof (D) == sizeof (C). Tôi đã thêm một báo cáo sizeof () cho bạn.
Thomas L Holaday
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.