Mẫu thiết kế tốt cho trình bao bọc c ++ xung quanh đối tượng ac


8

Tôi đã viết một trình bao bọc c ++ mở rộng xung quanh một thư viện c rất khó sử dụng nhưng cũng rất hữu ích. Mục tiêu là để có được sự kiên định của c ++ trong việc phân bổ đối tượng, phơi bày các thuộc tính của nó, giải phóng đối tượng, sao chép ngữ nghĩa, v.v ...

Vấn đề là ở đây: đôi khi thư viện c muốn đối tượng bên dưới (một con trỏ tới đối tượng) và hàm hủy lớp không được phá hủy bộ nhớ bên dưới. Trong khi hầu hết thời gian, hàm hủy nên phân bổ đối tượng bên dưới. Tôi đã thử nghiệm với việc thiết lập một bool hasOwnershipcờ trong lớp để hàm hủy, toán tử gán, v.v ... sẽ biết liệu nó có nên giải phóng bộ nhớ bên dưới hay không. Tuy nhiên, điều này là cồng kềnh cho người dùng, và đôi khi, không có cách nào để biết khi nào một quá trình khác sẽ sử dụng bộ nhớ đó.

Hiện tại, tôi đã thiết lập nó khi bài tập đến từ một con trỏ cùng loại với kiểu cơ bản, sau đó tôi đặt cờ hasOwnership. Tôi làm tương tự khi hàm tạo quá tải được gọi bằng cách sử dụng con trỏ từ thư viện c. Tuy nhiên, điều này vẫn không xử lý trường hợp khi người dùng đã tạo đối tượng và chuyển nó đến một trong các chức năng của tôi để gọi c_api và thư viện lưu trữ con trỏ để sử dụng sau. Nếu họ xóa đối tượng của mình, thì chắc chắn sẽ gây ra một segfault trong thư viện c.

Có một mẫu thiết kế sẽ đơn giản hóa quá trình này? Có lẽ một số loại tham khảo đếm?


bạn dường như không có bất kỳ suy nghĩ nào về sự an toàn của các vật thể này ...
ratchet freak

@ratchetfreak Tất cả các trình bao bọc đều gọi trình truy cập / trình biến đổi, đảm bảo ngữ nghĩa sao chép tốt, xử lý bộ nhớ, danh sách sao chép, v.v ... Tôi thực sự không chạm trực tiếp vào dữ liệu. Thư viện c là chủ đề an toàn. Ngoài ra, người dùng của tôi sẽ chỉ có quyền truy cập vào trình bao bọc của tôi một cách đồng bộ với hệ thống gọi lại, v.v. Khi cần thiết, tôi có khóa để đọc / ghi vào bất kỳ dữ liệu nào.
Jonathan Henson

2
unique_ptrtrong hầu hết các trường hợp có thể xử lý loại tài nguyên này, vì vậy bạn không cần phải tự thực hiện quản lý tài nguyên. unique_ptrsử dụng releasephương thức để từ bỏ quyền sở hữu đối tượng được lưu trữ.
Phi

Câu trả lời:


4

Nếu trách nhiệm dọn dẹp các công cụ được phân bổ động thay đổi giữa lớp trình bao bọc của bạn và thư viện C dựa trên cách mọi thứ được sử dụng, thì bạn đang xử lý một thư viện C được thiết kế tồi hoặc bạn đang cố gắng làm quá nhiều trong lớp trình bao bọc của mình.

Trong trường hợp đầu tiên, tất cả những gì bạn có thể làm là theo dõi xem ai chịu trách nhiệm dọn dẹp và hy vọng không có lỗi nào xảy ra (bởi bạn hoặc bởi những người duy trì thư viện C).

Trong trường hợp thứ hai, bạn nên suy nghĩ lại về thiết kế trình bao bọc của bạn. Có phải tất cả các chức năng thuộc về cùng một lớp, hoặc nó có thể được chia thành nhiều lớp. Có lẽ thư viện C sử dụng một cái gì đó tương tự như mẫu thiết kế Facade và bạn nên giữ một cấu trúc tương tự trong trình bao bọc C ++ của mình.

Trong mọi trường hợp, ngay cả khi thư viện C chịu trách nhiệm dọn dẹp một số nội dung, không có gì sai khi giữ một tham chiếu / con trỏ đến nội dung đó. Bạn chỉ cần nhớ rằng bạn không chịu trách nhiệm làm sạch những gì con trỏ đề cập đến.


Điều gì sẽ xảy ra nếu tôi đảm bảo một bản sao được tạo từ đối tượng cơ bản khi nó được chuyển đến lib. Sau đó, tôi có thể giải phóng bộ nhớ của mình khi tôi muốn và nó sẽ không ảnh hưởng gì. Tuy nhiên, sau đó tôi sẽ không nhận được các thay đổi đối tượng trong lớp.
Jonathan Henson

Và vâng, một số thực hành rất xấu đã được sử dụng trong lib này. Ví dụ: thay vì yêu cầu người dùng chuyển const char * cho hàm đặt chuỗi trong cấu trúc, sau đó tạo một bản sao để lưu trữ trong cấu trúc, họ hy vọng người dùng sẽ phân bổ chuỗi một cách nhanh chóng và truyền char * nó chỉ lưu trữ mà không tạo ra một bản sao. Đó là một trong những lý do chính khiến tôi thực hiện một trình bao bọc - để đảm bảo rằng ngữ nghĩa sao chép phù hợp đã được tuân theo.
Jonathan Henson

@JonathanHenson: Để khách hàng phân bổ thứ gì đó và chuyển quyền sở hữu / trách nhiệm của nó cho thư viện là một kỹ thuật tối ưu hóa phổ biến để tránh sao chép quá mức và hoàn toàn hợp lệ cho các thư viện C (miễn là nó được thực hiện một cách nhất quán). Nhưng nó thực sự có nghĩa là, khi được sử dụng từ C ++, người dùng có thể cần tạo thêm các bản sao để phù hợp với yêu cầu giao diện của thư viện C.
Bart van Ingen Schenau

3

Thường thì bạn có thể sử dụng một mẫu như thế này:

class C {
public:
  void foo() {
    underlying_foo(handle.get());
  }

  void bar() {
    // transfers ownership
    underlying_bar(handle.release());
  }

  // use default copy/move constructor and assignment operator

private:
  struct deleter {
    void operator()(T* ptr) {
      deleter_fn(ptr);
    }
  };
  std::unique_ptr<T, deleter> handle;
};

Bằng cách sử dụng, releasebạn có thể chuyển quyền sở hữu một cách rõ ràng. Tuy nhiên điều này là khó hiểu và bạn nên tránh nó nếu có thể.

Hầu hết các thư viện C đều có vòng đời đối tượng giống như C ++ (cấp phát đối tượng, truy cập, hủy) để ánh xạ độc đáo lên mẫu C ++ mà không cần chuyển quyền sở hữu.

Nếu người dùng cần quyền sở hữu chung, họ nên sử dụng shared_ptrvới các lớp của bạn. Đừng cố gắng thực hiện bất kỳ quyền sở hữu chia sẻ bản thân.


Cập nhật: Nếu bạn muốn chuyển quyền sở hữu rõ ràng hơn, bạn có thể sử dụng vòng loại tham chiếu:

void bar() && { ... }

Sau đó, người dùng phải gọi các bargiá trị như thế này:

C o;
std::move(o).bar();  // transfer of ownership is explicit at call site

@Phillip, có vẻ như chính xác những gì tôi cần. Tôi sẽ thử điều này và quay lại với bạn.
Jonathan Henson

Một câu hỏi. Chính xác quyền sở hữu IS trong trường hợp này. Liệu nó có nghĩa là đặc quyền truy cập / sửa đổi đối tượng, hay chỉ là khả năng xây dựng và phá hủy đối tượng?
Jonathan Henson

về cơ bản, một khi tôi chuyển bộ nhớ cơ bản cho c-lib, tôi không được phép thực hiện cuộc gọi miễn phí tới bộ nhớ. Mặc dù, tôi có thể và thường sẽ phải truy cập dữ liệu trong đối tượng.
Jonathan Henson

2
Quyền sở hữu là quyền tiêu diệt đối tượng.
DeadMG

@JonathanHenson: đó là một trường hợp thú vị không được đề cập ngay trong thành ngữ này: một khi bạn đã phát hành unique_ptr, nó sẽ không lưu trữ con trỏ mà nó giữ nữa. Mô hình hóa chuyển quyền sở hữu và đồng thời cho phép truy cập vào các tài nguyên bị từ chối dường như khá khó hiểu đối với người dùng. Làm thế nào để bạn kiểm soát khi thư viện C giải phóng các đối tượng mà nó hiện đang sở hữu?
Philipp

0

Có một câu trả lời thẳng thắn cho vấn đề của bạn, con trỏ thông minh. Bằng cách sử dụng một con trỏ thông minh để giữ bộ nhớ của thư viện C và thêm một tham chiếu khi con trỏ cũng ở trong thư viện (và thả tham chiếu khi thư viện C trở lại), sau đó bạn sẽ tự động giải phóng bộ nhớ khi số tham chiếu giảm xuống 0 (và chỉ sau đó)


Điều này không giải quyết được vấn đề, trừ khi tôi chỉ hiểu sai về con trỏ thông minh. Vấn đề là đôi khi, hàm hủy của tôi sẽ dọn sạch bộ nhớ, lần khác thư viện c sẽ dọn sạch bộ nhớ. Các cấu trúc này có init đặc biệt, chức năng miễn phí. Thư viện có thể tự gọi miễn phí, trước khi số tham chiếu của con trỏ thông minh chạm 0. Tôi cần quyết định cơ bản quyền sở hữu và để nó đi. Tôi không cần một con trỏ thông minh để biết khi nào cần dọn sạch bộ nhớ, vấn đề là đôi khi, tôi không phải là người chịu trách nhiệm dọn dẹp nó và tôi không cần phải giải phóng nó trong bộ hủy diệt - bao giờ hết.
Jonathan Henson

Đúng, tôi đồng ý, tôi hiểu sai vấn đề. Chỉ cần một quan sát, các thư viện khác nhau có chung bảng phân bổ bộ nhớ không? Công cụ C / C ++ mãnh liệt của tôi đã có từ 10 năm trước và trên Visual Studio hồi đó, việc phân bổ bộ nhớ trong một thư viện và giải phóng nó trong một thư viện khác sẽ bị rò rỉ bộ nhớ, trừ khi bạn thay đổi một số tùy chọn trình biên dịch cho các thư viện BOTH. Hậu quả của việc làm này sau đó sẽ là mâu thuẫn quản lý bộ nhớ trên các ứng dụng có luồng cao. Nó hoàn toàn có khả năng đây là thông tin lỗi thời mặc dù.
Michael Shaw

không, nếu mọi thứ được tải vào cùng một không gian địa chỉ, và nó là như vậy. Dù sao, tôi đang biên dịch lại c-lib trong cùng một cụm với trình bao bọc của tôi.
Jonathan Henson

0

Nếu thư viện có thể giải phóng mọi thứ trong nội bộ và các tình huống có thể xảy ra được ghi lại rõ ràng, thì tất cả những gì bạn có thể làm là đặt một lá cờ giống như bạn đang làm.


về cơ bản, một khi tôi chuyển bộ nhớ cơ bản cho c-lib, tôi không được phép thực hiện cuộc gọi miễn phí tới bộ nhớ. Mặc dù, tôi có thể và thường sẽ phải truy cập dữ liệu trong đối tượng.
Jonathan Henson
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.