Giao diện ngầm định và rõ ràng


9

Tôi nghĩ rằng tôi hiểu những hạn chế thực tế của đa hình thời gian biên dịch và đa hình thời gian chạy. Nhưng sự khác biệt về khái niệm giữa các giao diện rõ ràng (đa hình thời gian chạy. Tức là các hàm ảo và các con trỏ / tham chiếu) và các giao diện ẩn (đa hình thời gian biên dịch. Ví dụ: các mẫu) .

Tôi nghĩ rằng hai đối tượng cung cấp cùng một giao diện rõ ràng phải là cùng một loại đối tượng (hoặc có chung một tổ tiên), trong khi hai đối tượng cung cấp cùng một giao diện ngầm không cần phải là cùng một loại đối tượng và loại trừ ẩn giao diện mà cả hai cung cấp, có thể có chức năng khá khác nhau.

Bất kỳ suy nghĩ về điều này?

Và nếu hai đối tượng cung cấp cùng một giao diện ngầm, thì lý do gì (bên cạnh lợi ích kỹ thuật của việc không cần gửi công văn động với bảng tra cứu chức năng ảo, v.v.) sẽ không có các đối tượng này kế thừa từ một đối tượng cơ sở khai báo giao diện đó, do đó làm cho nó một giao diện rõ ràng ? Một cách khác để nói: bạn có thể cho tôi một trường hợp trong đó hai đối tượng cung cấp cùng một giao diện ẩn (và do đó có thể được sử dụng làm kiểu cho lớp mẫu mẫu) không nên kế thừa từ một lớp cơ sở làm cho giao diện đó rõ ràng không?

Một số bài viết liên quan:


Đây là một ví dụ để làm cho câu hỏi này cụ thể hơn:

Giao diện ngầm định:

class Class1
{
public:
  void interfaceFunc();
  void otherFunc1();
};

class Class2
{
public:
  void interfaceFunc();
  void otherFunc2();
};

template <typename T>
class UseClass
{
public:
  void run(T & obj)
  {
    obj.interfaceFunc();
  }
};

Giao diện rõ ràng:

class InterfaceClass
{
public:
  virtual void interfaceFunc() = 0;
};

class Class1 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc1();
};

class Class2 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc2();
};

class UseClass
{
public:
  void run(InterfaceClass & obj)
  {
    obj.interfaceFunc();
  }
};

Một ví dụ cụ thể hơn, sâu hơn:

Một số vấn đề C ++ có thể được giải quyết bằng một trong hai cách sau:

  1. một lớp templated có kiểu mẫu cung cấp một giao diện ngầm
  2. một lớp không có templated có một con trỏ lớp cơ sở cung cấp một giao diện rõ ràng

Mã không thay đổi:

class CoolClass
{
public:
  virtual void doSomethingCool() = 0;
  virtual void worthless() = 0;
};

class CoolA : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that an A would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

class CoolB : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that a B would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

Trường hợp 1 . Một lớp không có templated có một con trỏ lớp cơ sở cung cấp một giao diện rõ ràng:

class CoolClassUser
{
public:  
  void useCoolClass(CoolClass * coolClass)
  { coolClass.doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Trường hợp 2 . Một lớp templated có kiểu mẫu cung cấp một giao diện ngầm:

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser<CoolClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Trường hợp 3 . Một lớp templated có kiểu mẫu cung cấp một giao diện ẩn (lần này, không xuất phát từ CoolClass:

class RandomClass
{
public:
  void doSomethingCool()
  { /* Do cool stuff that a RandomClass would do */ }

  // I don't have to implement worthless()! Na na na na na!
}


template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  RandomClass * c1 = new RandomClass;
  RandomClass * c2 = new RandomClass;

  CoolClassUser<RandomClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Trường hợp 1 yêu cầu đối tượng được truyền vào phải useCoolClass()là con của CoolClass(và hiện thực worthless()). Trường hợp 2 và 3, mặt khác, sẽ lấy bất kỳ lớp nào có doSomethingCool()chức năng.

Nếu người dùng mã luôn luôn được phân lớp tốt CoolClass, thì Trường hợp 1 có ý nghĩa trực quan, vì họ CoolClassUsersẽ luôn mong đợi việc thực hiện a CoolClass. Nhưng giả sử mã này sẽ là một phần của khung API, vì vậy tôi không thể dự đoán liệu người dùng có muốn phân lớp CoolClasshoặc cuộn lớp riêng của họ có doSomethingCool()chức năng hay không.


Có thể tôi đang thiếu một cái gì đó, nhưng không phải là sự khác biệt quan trọng đã được nêu rõ ràng trong đoạn đầu tiên của bạn, đó là các giao diện rõ ràng là đa hình thời gian chạy, trong khi các giao diện ngầm là đa hình thời gian biên dịch?
Robert Harvey

2
Có một số vấn đề có thể được giải quyết bằng cách có Class hoặc hàm đưa con trỏ đến Class trừu tượng (cung cấp giao diện rõ ràng) hoặc bằng cách sử dụng Class templated hoặc hàm sử dụng một đối tượng cung cấp giao diện ẩn. Cả hai giải pháp đều hoạt động. Khi nào bạn muốn sử dụng giải pháp đầu tiên? Thư hai?
Chris Morris

tôi nghĩ rằng hầu hết những cân nhắc này sụp đổ khi bạn mở các khái niệm nhiều hơn một chút. ví dụ, nơi bạn sẽ phù hợp với đa hình tĩnh không kế thừa?
Javier

Câu trả lời:


8

Bạn đã xác định điểm quan trọng - một là thời gian chạy và điểm còn lại là thời gian biên dịch . Thông tin thực sự bạn cần là sự phân nhánh của sự lựa chọn này.

Thời gian biên dịch:

  • Pro: Giao diện thời gian biên dịch chi tiết hơn nhiều so với giao diện thời gian chạy. Do đó, điều tôi muốn nói là bạn chỉ có thể sử dụng các yêu cầu của một chức năng hoặc một bộ chức năng, khi bạn gọi chúng. Bạn không phải luôn luôn làm toàn bộ giao diện. Các yêu cầu là duy nhất và chính xác những gì bạn cần.
  • Pro: Các kỹ thuật như CRTP có nghĩa là bạn có thể sử dụng các giao diện ngầm để triển khai mặc định những thứ như toán tử. Bạn không bao giờ có thể làm một điều như vậy với thừa kế thời gian chạy.
  • Pro: giao diện Implicit là nhiều dễ dàng hơn để soạn và nhân "kế thừa" so với giao diện thời gian chạy, và không áp đặt bất kỳ loại restrictions- nhị phân ví dụ, các lớp học POD có thể sử dụng giao diện ngầm. Không cần virtualthừa kế hoặc các shenanigans khác với giao diện ngầm - một lợi thế lớn.
  • Pro: Trình biên dịch có thể thực hiện tối ưu hóa nhiều hơn cho các giao diện thời gian biên dịch. Ngoài ra, an toàn loại thêm làm cho mã an toàn hơn.
  • Pro: Không thể thực hiện nhập giá trị cho giao diện thời gian chạy, vì bạn không biết kích thước hoặc căn chỉnh của đối tượng cuối cùng. Điều này có nghĩa là bất kỳ trường hợp nào cần / lợi ích từ việc gõ giá trị đều thu được lợi ích lớn từ các mẫu.
  • Con: Các mẫu là một bitch để biên dịch và sử dụng, và chúng có thể được chuyển một cách khéo léo giữa các trình biên dịch
  • Con: Mẫu không thể được tải vào thời gian chạy (rõ ràng), vì vậy chúng có giới hạn trong việc thể hiện cấu trúc dữ liệu động, ví dụ.

Thời gian chạy:

  • Pro: Loại cuối cùng không phải quyết định cho đến thời gian chạy. Điều này có nghĩa là kế thừa thời gian chạy có thể biểu thị một số cấu trúc dữ liệu dễ dàng hơn nhiều, nếu các mẫu có thể làm điều đó. Ngoài ra, bạn có thể xuất các loại đa hình thời gian chạy qua các ranh giới C, ví dụ: COM.
  • Pro: Việc xác định và triển khai kế thừa thời gian chạy dễ dàng hơn nhiều và bạn sẽ không thực sự có bất kỳ hành vi cụ thể nào của trình biên dịch.
  • Con: Kế thừa thời gian chạy có thể chậm hơn kế thừa thời gian biên dịch.
  • Con: Kế thừa thời gian chạy mất thông tin loại.
  • Con: Kế thừa thời gian chạy là cách kém linh hoạt.
  • Con: Đa thừa kế là một con chó cái.

Đưa ra danh sách tương đối, nếu bạn không cần một lợi thế cụ thể của kế thừa thời gian chạy, không sử dụng nó. Nó chậm hơn, kém linh hoạt hơn và kém an toàn hơn các mẫu.

Chỉnh sửa: Đáng chú ý là trong C ++, đặc biệt có những cách sử dụng cho kế thừa khác với đa hình thời gian chạy. Ví dụ: bạn có thể kế thừa typedefs hoặc sử dụng nó để gắn thẻ loại hoặc sử dụng CRTP. Cuối cùng, mặc dù, các kỹ thuật này (và các kỹ thuật khác) thực sự thuộc "Thời gian biên dịch", mặc dù chúng được thực hiện bằng cách sử dụng class X : public Y.


Về pro đầu tiên của bạn cho compXLime, điều này có liên quan đến một trong những câu hỏi chính của tôi. Bạn có bao giờ muốn làm rõ rằng bạn chỉ muốn làm việc với một giao diện rõ ràng. I E. 'Tôi không quan tâm nếu bạn có tất cả các chức năng tôi yêu cầu, nếu bạn không kế thừa từ Lớp Z thì tôi không muốn làm gì với bạn'. Ngoài ra, kế thừa thời gian chạy không mất thông tin loại khi sử dụng con trỏ / tham chiếu, đúng không?
Chris Morris

@ChrisMorris: Không. Nếu nó hoạt động thì nó hoạt động, đó là tất cả những gì bạn nên quan tâm. Tại sao làm cho ai đó viết cùng một mã chính xác ở nơi khác?
jmoreno

1
@ChrisMorris: Không, tôi sẽ không. Nếu tôi chỉ cần X, thì đó là một trong những nguyên tắc cơ bản của đóng gói mà tôi chỉ nên yêu cầu và quan tâm đến X. Ngoài ra, nó sẽ mất thông tin loại. Bạn không thể, ví dụ, ngăn xếp phân bổ một đối tượng thuộc loại đó. Bạn không thể khởi tạo một mẫu với loại đúng. Bạn không thể gọi các hàm thành viên templated trên chúng.
DeadMG

Điều gì về một tình huống mà bạn có một lớp Q sử dụng một số lớp. Q lấy một tham số mẫu, do đó, bất kỳ lớp nào cung cấp giao diện ngầm sẽ làm, hoặc chúng tôi nghĩ vậy. Hóa ra lớp Q cũng mong muốn lớp bên trong của nó (gọi nó là H) sử dụng giao diện của Q. Ví dụ, khi đối tượng H bị phá hủy, nó sẽ gọi một số chức năng của Q. Điều này không thể được chỉ định trong một giao diện ngầm. Do đó, các mẫu thất bại. Nói rõ hơn, một tập hợp các lớp được kết hợp chặt chẽ đòi hỏi nhiều hơn là các giao diện ngầm ẩn lẫn nhau dường như làm suy yếu việc sử dụng các mẫu.
Chris Morris

Con compXLime: Xấu xí để gỡ lỗi, cần phải đặt các định nghĩa vào tiêu đề
JFFIGK
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.