Có sử dụng hợp pháp cho void * không?


87

Có sử dụng hợp pháp void*trong C ++ không? Hay cái này được giới thiệu vì C đã có nó?

Chỉ để tóm tắt suy nghĩ của tôi:

Đầu vào : Nếu chúng ta muốn cho phép nhiều kiểu đầu vào, chúng ta có thể nạp chồng các hàm và phương thức, hoặc chúng ta có thể xác định một lớp cơ sở chung hoặc mẫu (cảm ơn vì đã đề cập đến vấn đề này trong câu trả lời). Trong cả hai trường hợp, mã được mô tả nhiều hơn và ít bị lỗi hơn (miễn là lớp cơ sở được triển khai một cách lành mạnh).

Đầu ra : Tôi không thể nghĩ ra bất kỳ tình huống nào mà tôi muốn nhận thay void*vì thứ gì đó có nguồn gốc từ một lớp cơ sở đã biết.

Chỉ để nói rõ ý tôi: Tôi không hỏi cụ thể liệu có trường hợp sử dụng cho void*, nhưng nếu có trường hợp thì đâu void*là lựa chọn tốt nhất hoặc duy nhất. Điều đó đã được trả lời hoàn hảo bởi một số người dưới đây.


3
Còn khi bạn muốn có nhiều kiểu như int & std :: string thì sao?
Amir

12
@Amir, variant, any, gắn thẻ công đoàn. Bất kỳ thứ gì có thể cho bạn biết loại nội dung thực tế và an toàn hơn khi sử dụng.
Revolver_Ocelot

15
"C has it" là một lời biện minh đủ mạnh, không cần phải tìm kiếm thêm. Tránh nó càng nhiều càng tốt là một điều tốt trong cả hai ngôn ngữ.
n. 'đại từ' m.

1
Nói một điều: việc tương tác với API kiểu C sẽ rất khó xử nếu không có nó.
Martin James

1
Một cách sử dụng thú vị là loại chô bôi cho vectơ của con trỏ
Paolo M

Câu trả lời:


81

void*ít nhất là cần thiết vì là kết quả của ::operator new(cũng như mọi operator new...) và của mallocvà là đối số của newtoán tử vị trí .

void*có thể được coi là siêu kiểu chung của mọi kiểu con trỏ. Vì vậy, nó không có nghĩa chính xác là con trỏ tới void, mà là con trỏ tới bất kỳ thứ gì.

BTW, nếu bạn muốn giữ một số dữ liệu cho một số biến toàn cầu không liên quan, bạn có thể sử dụng một số std::map<void*,int> score; sau đó, sau khi đã khai báo toàn cục int x;double y;std::string s;làm score[&x]=1;score[&y]=2;score[&z]=3;

memset muốn một void*địa chỉ (những địa chỉ chung chung nhất)

Ngoài ra, hệ thống POSIX có dlsym và kiểu trả về của nó rõ ràng phải làvoid*


1
Đó là một điểm rất tốt. Tôi quên đề cập rằng tôi đang suy nghĩ nhiều hơn từ một người dùng ngôn ngữ hơn là từ phía triển khai. Một điều nữa mà tôi không hiểu: Có phải chức năng mới không? Tôi nghĩ về nó nhiều hơn như một từ khóa
magu_ 14/12/15

9
new là một toán tử, như + và sizeof.
Joshua

7
Tất nhiên bộ nhớ cũng có thể được trả lại thông qua một char *. Chúng rất gần gũi về khía cạnh này, mặc dù ý nghĩa hơi khác một chút.
edmz

@Joshua. Không biết điều đó. Cảm ơn bạn đã dạy tôi điều này. Vì C++được viết trong C++này là lý do đủ để có void*. Gotta thích trang web này. Hỏi một câu hỏi sẽ học được nhiều điều.
magu_

3
@black Trở lại trong những ngày cũ, C thậm chí không một void*loại. Tất cả các chức năng thư viện tiêu chuẩn được sử dụngchar*
Cole Johnson

28

Có nhiều lý do để sử dụng void*, 3 lý do phổ biến nhất:

  1. tương tác với thư viện C bằng cách sử dụng void*trong giao diện của nó
  2. loại-tẩy
  3. biểu thị bộ nhớ chưa được đánh máy

Theo thứ tự ngược lại, việc biểu thị bộ nhớ chưa được đánh máy bằng void*(3) thay vì char*(hoặc các biến thể) giúp ngăn ngừa số học con trỏ ngẫu nhiên; có rất ít hoạt động có sẵn void*vì vậy nó thường yêu cầu truyền trước khi hữu ích. Và tất nhiên, giống như char*không có vấn đề gì với răng cưa.

Type-erasure (2) vẫn được sử dụng trong C ++, cùng với các mẫu hoặc không:

  • mã không chung chung giúp giảm khối lượng nhị phân, nó hữu ích trong các đường dẫn lạnh ngay cả trong mã chung
  • mã không chung chung là cần thiết để lưu trữ đôi khi, ngay cả trong vùng chứa chung như std::function

Và rõ ràng, khi giao diện bạn xử lý các mục đích sử dụng void*(1), bạn có rất ít lựa chọn.


15

Ồ vâng. Ngay cả trong C ++, đôi khi chúng ta đi với void *hơn là template<class T*>vì đôi khi mã bổ sung từ việc mở rộng khuôn mẫu nặng quá nhiều.

Thông thường, tôi sẽ sử dụng nó làm kiểu triển khai thực tế và kiểu mẫu sẽ kế thừa từ nó và bao bọc các phôi.

Ngoài ra, trình phân bổ phiến tùy chỉnh (triển khai mới của nhà điều hành) phải sử dụng void *. Đây là một trong những lý do tại sao g ++ thêm phần mở rộng cho phép con trỏ số học trên void *như thể nó có kích thước 1.


Cảm ơn về câu trả lời của bạn. Bạn nói đúng, tôi đã quên đề cập đến các mẫu. Nhưng sẽ không mẫu được thậm chí phù hợp tốt hơn cho nhiệm vụ kể từ khi hiếm khi giới thiệu một hình phạt hiệu quả (? Stackoverflow.com/questions/2442358/... )
magu_

1
Các mẫu đưa ra hình phạt về kích thước mã, đây cũng là hình phạt về hiệu suất trong hầu hết các trường hợp.
Joshua

struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;là một khoảng trống tối thiểu - * - trình mô phỏng bằng cách sử dụng các mẫu.
user253751 14/12/15

3
@Joshua: Bạn sẽ cần trích dẫn cho "hầu hết".
user541686

@Mehrdad: Xem bộ nhớ cache L1.
Joshua

10

Đầu vào: Nếu chúng ta muốn cho phép nhiều kiểu đầu vào, chúng ta có thể nạp chồng các hàm và phương thức

Thật.

cách khác, chúng ta có thể xác định một lớp cơ sở chung.

Điều này đúng một phần: điều gì sẽ xảy ra nếu bạn không thể xác định một lớp cơ sở chung, một giao diện hoặc tương tự? Để xác định những người bạn cần có quyền truy cập vào mã nguồn, điều này thường không thể thực hiện được.

Bạn đã không đề cập đến các mẫu. Tuy nhiên, các mẫu không thể giúp bạn với tính đa hình: chúng hoạt động với các kiểu tĩnh, tức là đã biết tại thời điểm biên dịch.

void*có thể coi là mẫu số chung thấp nhất. Trong C ++, bạn thường không cần nó vì (i) bạn vốn dĩ không thể làm được gì nhiều với nó và (ii) hầu như luôn có những giải pháp tốt hơn.

Hơn nữa, bạn thường sẽ chuyển đổi nó sang các loại bê tông khác. Đó là lý do tại sao char *thường tốt hơn, mặc dù nó có thể chỉ ra rằng bạn đang mong đợi một chuỗi kiểu C, thay vì một khối dữ liệu thuần túy. Đó là lý do tại sao void*tốt hơn char*là vì nó cho phép ép kiểu ngầm từ các kiểu con trỏ khác.

Bạn phải nhận một số dữ liệu, làm việc với nó và tạo ra một đầu ra; để đạt được điều đó, bạn cần biết dữ liệu bạn đang làm việc, nếu không, bạn gặp phải một vấn đề khác không phải là vấn đề bạn đang giải quyết ban đầu. void*Ví dụ, nhiều ngôn ngữ không có và không có vấn đề gì với điều đó.

Sử dụng hợp pháp khác

Khi in các địa chỉ con trỏ với các chức năng như printfcon trỏ phải có void*kiểu và do đó, bạn có thể cần ép kiểu thành void*


7

Vâng, nó hữu ích như bất kỳ thứ nào khác trong ngôn ngữ.
Ví dụ: bạn có thể sử dụng nó để xóa loại lớp mà bạn có thể truyền tĩnh sang loại phù hợp khi cần thiết, để có một giao diện tối giản và linh hoạt.

Trong đó phản ứng có một ví dụ về sử dụng mà nên cung cấp cho bạn một ý tưởng.
Tôi sao chép và dán nó bên dưới vì mục đích rõ ràng:

class Dispatcher {
    Dispatcher() { }

    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    static Dispatcher create(C *instance) {
        Dispatcher d;
        d.fn = &invoke<C, M>;
        d.instance = instance;
        return d;
    }

    void operator()() {
        (fn)(instance);
    }

private:
    using Fn = void(*)(void *);
    Fn fn;
    void *instance;
};

Rõ ràng, đây chỉ là một trong số rất nhiều công dụng của void*.


4

Giao diện với một hàm thư viện bên ngoài trả về một con trỏ. Đây là một cho một ứng dụng Ada.

extern "C" { void* ada_function();}

void* m_status_ptr = ada_function();

Điều này trả về một con trỏ đến bất cứ điều gì Ada muốn nói với bạn. Bạn không cần phải làm bất cứ điều gì cầu kỳ với nó, bạn có thể đưa nó lại cho Ada để làm việc tiếp theo. Trên thực tế, việc gỡ rối một con trỏ Ada trong C ++ là không hề nhỏ.


Chúng tôi không thể sử dụng autothay thế trong trường hợp này? Giả sử rằng kiểu được biết tại thời điểm biên dịch.
magu_

Ah, ngày nay chúng ta có thể có. Mã đó là từ một số năm trước khi tôi tạo một số trình bao bọc Ada.
RedSonja

Các loại Ada có thể rất quỷ dị - bạn biết đấy, chúng tự làm ra nó và có một thú vui gian trá là làm cho nó trở nên phức tạp. Tôi không được phép thay đổi giao diện, điều đó quá dễ dàng và nó trả về một số thứ khó chịu ẩn trong những con trỏ trống đó. Ặc.
RedSonja

2

Tóm lại, C ++ là một ngôn ngữ nghiêm ngặt (không tính đến các di tích C như malloc () ) yêu cầu void * vì nó không có cha mẹ chung của tất cả các kiểu có thể có. Không giống như objC, ví dụ, có đối tượng .


mallocnewcả hai trở lại void *, vì vậy bạn sẽ cần nếu ngay cả nếu có một lớp đối tượng trong C ++
Dmitry Grigoryev

malloc là đang bị diệt vong, nhưng trong ngôn ngữ nghiêm ngặt mới nên trả lại đối tượng *
nredko

làm thế nào để bạn phân bổ một mảng các số nguyên sau đó?
Dmitry Grigoryev

@DmitryGrigoryev operator new()trả lại void *, nhưng newbiểu thức thì không
MM

1. Tôi không chắc mình muốn thấy một đối tượng lớp cơ sở ảo phía trên mọi lớp và kiểu , bao gồm cả int2. nếu nó không phải là EBC ảo, thì nó khác với nó như thế nào void*?
lorro

1

Điều đầu tiên xuất hiện trong đầu tôi (mà tôi nghi ngờ là một trường hợp cụ thể của một vài câu trả lời ở trên) là khả năng truyền một cá thể đối tượng đến một threadproc trong Windows.

Tôi có một vài lớp C ++ cần thực hiện việc này, chúng có các triển khai luồng công nhân và tham số LPVOID trong API CreateThread () nhận được địa chỉ của việc triển khai phương thức tĩnh trong lớp để luồng công nhân có thể thực hiện công việc với một phiên bản cụ thể của lớp. Việc ép kiểu tĩnh đơn giản trở lại trong threadproc mang lại thể hiện để làm việc, cho phép mỗi đối tượng khởi tạo có một luồng công nhân từ một phương thức tĩnh duy nhất.


0

Trong trường hợp đa kế thừa, nếu bạn cần lấy một con trỏ đến byte đầu tiên của đoạn bộ nhớ bị chiếm bởi một đối tượng, bạn có thể dynamic_castlàm như vậy void*.

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.