Sự khác biệt giữa điều phối động và liên kết muộn trong C ++ là gì?


76

Gần đây tôi đã đọc về Công văn động trên Wikipedia và không thể hiểu được sự khác biệt giữa công văn động và ràng buộc muộn trong C ++.

Khi mỗi một trong các cơ chế được sử dụng?

Trích dẫn chính xác từ Wikipedia:

Công văn động khác với ràng buộc muộn (hay còn gọi là ràng buộc động). Trong ngữ cảnh chọn một hoạt động, ràng buộc đề cập đến quá trình liên kết tên với một hoạt động. Điều phối đề cập đến việc chọn một triển khai cho hoạt động sau khi bạn đã quyết định hoạt động mà tên đề cập đến. Với điều phối động, tên có thể được liên kết với một hoạt động đa hình tại thời điểm biên dịch, nhưng việc triển khai không được chọn cho đến thời gian chạy (đây là cách điều khiển động hoạt động trong C ++). Tuy nhiên, liên kết trễ ngụ ý điều phối động vì bạn không thể chọn triển khai hoạt động đa hình nào để chọn cho đến khi bạn đã chọn hoạt động mà tên đề cập đến.


2
Đó là một câu hỏi hay và có thể tốt hơn nếu bạn đề cập đến các liên kết bạn đã đọc.
masoud

Câu trả lời:


73

Một câu trả lời khá phù hợp cho điều này thực sự được đưa vào một câu hỏi về ràng buộc muộn so với sớm trên programmers.stackexchange.com .

Nói tóm lại, liên kết trễ đề cập đến phía đối tượng của một eval, động đề cập đến phía chức năng. Trong quá trình ràng buộc muộn, kiểu của một biến là biến thể trong thời gian chạy. Trong điều phối động, hàm hoặc chương trình con đang được thực thi là một biến thể.

Trong C ++, chúng ta không thực sự có liên kết trễ vì kiểu đã biết (không nhất thiết phải là phần cuối của hệ thống phân cấp kế thừa, nhưng ít nhất là một lớp cơ sở chính thức hoặc giao diện). Nhưng chúng ta làm có công văn năng động qua các phương pháp ảo và đa hình.

Ví dụ tốt nhất mà tôi có thể cung cấp cho liên kết muộn là "đối tượng" không định kiểu trong Visual Basic. Môi trường thời gian chạy thực hiện tất cả các công việc nặng nhọc ràng buộc muộn cho bạn.

Dim obj

- initialize object then..
obj.DoSomething()

Trình biên dịch sẽ thực sự viết mã ngữ cảnh thực thi thích hợp cho runtime-engine để thực hiện tra cứu có tên của phương thức được gọi DoSomethingvà nếu được phát hiện với các tham số khớp đúng, sẽ thực hiện lệnh gọi cơ bản. Trong thực tế, một cái gì đó về kiểu của đối tượng đã được biết đến (nó kế thừa IDispatchvà hỗ trợ GetIDsOfNames(), v.v.). nhưng đối với ngôn ngữ có liên quan, kiểu của biến hoàn toàn không được biết tại thời điểm biên dịch và nó không có ý tưởng nếu DoSomethingthậm chí là một phương thức cho bất cứ điều gì objthực sự cho đến khi thời gian chạy đạt đến điểm thực thi.

Tôi sẽ không bận tâm đến việc kết xuất giao diện ảo C ++ et'al, vì tôi tin rằng bạn đã biết chúng trông như thế nào. Tôi hy vọng rõ ràng là ngôn ngữ C ++ không thể làm được điều này. Nó được đánh máy mạnh. Nó có thể (và hiển nhiên) thực hiện điều phối động thông qua tính năng phương thức ảo đa hình.


2
Tôi đã cố gắng hết sức để ủng hộ câu trả lời này, điều đó thực sự giải thích sự khác biệt. Quá xấu, OP đã bị mù do 131k đại diện - và chọn câu trả lời tồi tệ nhất có thể ...
IInspectable

1
@IInspectable, tất cả đều tốt. Tôi thường bỏ bất kỳ câu trả lời nào tôi đăng mà không có phiếu bầu nào sau một tuần hoặc lâu hơn bởi vì nếu không ai thấy chúng hữu ích, tôi không muốn họ dính vào lộn xộn những câu trả lời "được chấp nhận". Nhưng tôi rất vui vì ai đó đã tìm thấy lời giải thích khác biệt ở đây đáng giá, vì vậy tôi có thể sẽ giữ nó ngay bây giờ. Cảm ơn về sự hỗ trợ.
WhozCraig

Điều phối động chỉ là một trường hợp cụ thể của liên kết trễ, trong đó bộ chọn phương thức là tên và phương thức đích là phương thức cần được giải quyết. Công văn động về cơ bản là một ràng buộc muộn được đánh giá một phần.
naasking

2
@ZanLynx chúng khác nhau. Trong VB, langauge không biết gì về đối tượng đó (hoặc thậm chí nó là một đối tượng hợp lệ) ngoài việc bạn (chương trình) muốn kích hoạt một phương thức được gọi DoSomthing. Đừng nhầm lẫn ngôn ngữ với thời gian chạy . Tôi đã cố gắng làm rõ điều đó, nhưng thành thật mà nói, bản thân tôi chỉ hài lòng một chút với mô tả. Về phía C ++, bạn sẽ phải tự mình mã hóa tất cả các động lực thông qua IDispatch, và thậm chí sau đó, ngôn ngữ biết một số thứ của đối tượng (nó hỗ trợ, IDispatchv.v.).
WhozCraig

1
Tôi có đúng khi giả định rằng Java chỉ có điều phối động, vì lớp của Đối tượng luôn được biết đến theo nghĩa đó, và tôi trích dẫn (không nhất thiết phải là phần cuối của hệ thống phân cấp kế thừa, nhưng ít nhất là một lớp cơ sở chính thức hoặc giao diện) . Nếu không thì đâu sẽ là một ví dụ về tính đa hình?
YellowPillow

8

Liên kết muộn là gọi một phương thức theo tên trong thời gian chạy. Bạn không thực sự có điều này trong c ++, ngoại trừ việc nhập các phương thức từ DLL.
Một ví dụ cho điều đó sẽ là: GetProcAddress ()

Với công văn động, trình biên dịch có đủ thông tin để gọi việc triển khai đúng phương thức. Điều này thường được thực hiện bằng cách tạo một bảng ảo .


8

Chính liên kết đã giải thích sự khác biệt:

Công văn động khác với ràng buộc muộn (hay còn gọi là ràng buộc động). Trong ngữ cảnh chọn một hoạt động, ràng buộc đề cập đến quá trình liên kết tên với một hoạt động. Điều phối đề cập đến việc chọn một triển khai cho hoạt động sau khi bạn đã quyết định hoạt động mà tên đề cập đến.

Với điều phối động, tên có thể được liên kết với một hoạt động đa hình tại thời điểm biên dịch, nhưng việc triển khai không được chọn cho đến thời gian chạy (đây là cách điều khiển động hoạt động trong C ++). Tuy nhiên, liên kết trễ ngụ ý điều phối động vì bạn không thể chọn triển khai hoạt động đa hình nào để chọn cho đến khi bạn đã chọn hoạt động mà tên đề cập đến.

Nhưng chúng chủ yếu bằng nhau trong C ++, bạn có thể thực hiện điều phối động bằng các hàm ảo và vtables.

C ++ sử dụng liên kết sớm và cung cấp cả công văn động và tĩnh. Hình thức công văn mặc định là tĩnh. Để nhận được công văn động, bạn phải khai báo một phương thức là ảo.


6

Trong C ++, cả hai đều giống nhau.

Trong C ++, có hai loại ràng buộc:

  • liên kết tĩnh - được thực hiện tại thời điểm biên dịch.
  • liên kết động - được thực hiện trong thời gian chạy.

Liên kết động, vì nó được thực hiện trong thời gian chạy, còn được gọi là ràng buộc muộn và ràng buộc tĩnh đôi khi được gọi là ràng buộc sớm .

Sử dụng liên kết động, C ++ hỗ trợ đa hình thời gian chạy thông qua các hàm ảo (hoặc con trỏ hàm ) và sử dụng liên kết tĩnh, tất cả các lệnh gọi hàm khác đều được giải quyết.


5

Ràng buộc đề cập đến quá trình liên kết tên với một phép toán.

điều chính ở đây là các tham số hàm này quyết định hàm nào sẽ gọi trong thời gian chạy

Điều phối đề cập đến việc chọn một triển khai cho hoạt động sau khi bạn đã quyết định hoạt động mà tên đề cập đến.

điều khiển cử đến điều đó theo tham số khớp

http://en.wikipedia.org/wiki/Dynamic_dispatch

hy vọng điều này sẽ giúp bạn


3

Hãy để tôi cho bạn một ví dụ về sự khác biệt bởi vì chúng KHÔNG giống nhau. Có, điều phối động cho phép bạn chọn đúng phương thức khi bạn đang tham chiếu đến một đối tượng bởi một lớp cha, nhưng phép thuật đó rất cụ thể đối với hệ thống phân cấp lớp đó và bạn phải thực hiện một số khai báo trong lớp cơ sở để làm cho nó hoạt động (các phương thức trừu tượng điền vào các vtables vì ​​chỉ mục của phương thức trong bảng không thể thay đổi giữa các loại cụ thể). Vì vậy, bạn có thể gọi tất cả các phương thức trong Tabby và Sư tử và Hổ bằng một con trỏ Mèo chung và thậm chí có các mảng Mèo chứa đầy Sư tử và Hổ và Tabbys. Nó biết những gì lập chỉ mục các phương thức đó tham chiếu đến vtable của đối tượng tại thời điểm biên dịch (liên kết tĩnh / sớm), ngay cả khi phương thức được chọn tại thời điểm chạy (điều phối động).

Bây giờ, hãy triển khai một mảng có chứa Sư tử và Hổ và Gấu! ((Ôi trời!)). Giả sử chúng ta không có lớp cơ sở được gọi là Animal, trong C ++, bạn sẽ có nhiều việc phải làm vì trình biên dịch sẽ không cho phép bạn thực hiện bất kỳ công văn động nào mà không có lớp cơ sở chung. Các chỉ mục cho các vtables cần phải khớp và không thể thực hiện được giữa các lớp chưa được cấp. Bạn cần phải có một vtable đủ lớn để chứa các phương thức ảo của tất cả các lớp trong hệ thống. Các lập trình viên C ++ hiếm khi coi đây là một hạn chế bởi vì bạn đã được đào tạo để suy nghĩ một cách nhất định về thiết kế lớp. Tôi không nói nó tốt hơn hay tệ hơn.

Với ràng buộc muộn, thời gian chạy sẽ xử lý điều này mà không có lớp cơ sở chung. Thông thường có một hệ thống bảng băm được sử dụng để tìm các phương thức trong các lớp với hệ thống bộ đệm được sử dụng trong trình điều phối. Ở đâu trong C ++, trình biên dịch biết tất cả các kiểu. Trong một ngôn ngữ có giới hạn cuối, các đối tượng tự biết loại của chúng (không phải là không đánh máy, bản thân các đối tượng biết chính xác họ là ai trong hầu hết các trường hợp). Điều này có nghĩa là tôi có thể có các mảng gồm nhiều loại đối tượng nếu tôi muốn (Sư tử và Hổ và Gấu). Và bạn có thể triển khai chuyển tiếp và tạo mẫu tin nhắn (cho phép thay đổi hành vi trên mỗi đối tượng mà không cần thay đổi lớp) và tất cả các loại khác theo những cách linh hoạt hơn nhiều và dẫn đến ít chi phí mã hơn so với các ngôn ngữ không hỗ trợ ràng buộc muộn .

Bạn đã bao giờ lập trình trong Android và sử dụng findViewById ()? Bạn hầu như luôn kết thúc việc ép kiểu kết quả để có được kiểu phù hợp, và việc ép kiểu về cơ bản nằm ở trình biên dịch và từ bỏ tất cả tính tốt của việc kiểm tra kiểu tĩnh được cho là làm cho ngôn ngữ tĩnh vượt trội hơn. Tất nhiên, thay vào đó bạn có thể có findTextViewById (), findEditTextById () và một triệu người khác để các kiểu trả về của bạn khớp nhau, nhưng điều đó đang ném tính đa hình ra ngoài cửa sổ; được cho là toàn bộ cơ sở của OOP. Một ngôn ngữ bị ràng buộc muộn có thể cho phép bạn lập chỉ mục đơn giản theo một ID và coi nó như một bảng băm và không cần quan tâm đến kiểu đang được lập chỉ mục hay được trả về.

Đây là một ví dụ khác. Giả sử rằng bạn có đẳng cấp Sư tử và hành vi mặc định của nó là ăn thịt bạn khi bạn nhìn thấy nó. Trong C ++, nếu bạn muốn có một sư tử "được huấn luyện", bạn cần tạo một lớp con mới. Việc tạo mẫu sẽ cho phép bạn thay đổi một hoặc hai phương pháp của Sư Tử cụ thể đó cần được thay đổi. Đó là lớp và kiểu không thay đổi. C ++ không thể làm điều đó. Điều này rất quan trọng vì khi bạn có một "AfricanSpottedLion" mới kế thừa từ Lion, bạn cũng có thể huấn luyện nó. Việc tạo mẫu không thay đổi cấu trúc lớp để nó có thể được mở rộng. Đây thông thường là cách các ngôn ngữ này xử lý các vấn đề thường yêu cầu đa kế thừa, hoặc có lẽ đa kế thừa là cách bạn xử lý việc thiếu nguyên mẫu.

FYI, Objective-C là C với thông điệp của SmallTalk được thêm vào và SmallTalk là OOP ban đầu và cả hai đều bị ràng buộc muộn với tất cả các tính năng ở trên và hơn thế nữa. Các ngôn ngữ bị ràng buộc muộn có thể chậm hơn một chút so với quan điểm ở cấp vi mô, nhưng thường có thể cho phép mã được cấu trúc theo cách hiệu quả hơn ở cấp vĩ mô và tất cả đều được ưu tiên.


2

Với định nghĩa dài dòng đó trên Wikipedia, tôi muốn phân loại cử động động là ràng buộc muộn của C ++

struct Base {
    virtual void foo(); // Dynamic dispatch according to Wikipedia definition
    void bar();         // Static dispatch according to Wikipedia definition
};

Thay vào đó, ràng buộc muộn, đối với Wikipedia, dường như có nghĩa là gửi con trỏ đến thành viên của C ++

(this->*mptr)();

trong đó việc lựa chọn thao tác đang được gọi là gì (chứ không phải chỉ việc triển khai nào) được thực hiện trong thời gian chạy.

Tuy nhiên, trong tài liệu C ++ late bindingthường được sử dụng cho cái mà Wikipedia gọi là công văn động.


1

Câu hỏi này có thể giúp bạn.

Công văn động thường đề cập đến nhiều công văn.

Hãy xem xét ví dụ dưới đây. Tôi hy vọng nó có thể giúp bạn.

    class Base2;
    class Derived2; //Derived2 class is child of Base2
class Base1 {
    public:
        virtual void function1 (Base2 *);
        virtual void function1 (Derived2 *);
}

class Derived1: public Base1 {
    public:
    //override.
    virtual void function1(Base2 *);
    virtual void function1(Derived2 *);
};

Hãy xem xét trường hợp dưới đây.

Derived1 * d = new Derived1;
Base2 * b = new Derived2;

//Now which function1 will be called.
d->function1(b);

Nó sẽ gọi là không function1lấy . Điều này là do thiếu nhiều công văn năng động.Base2*Derived2*

Ràng buộc muộn là một trong những cơ chế để thực hiện một công văn động.


1

Điều phối động là những gì sẽ xảy ra khi bạn sử dụng virtualtừ khóa trong C ++. Ví dụ:

struct Base
{
    virtual int method1() { return 1; }
    virtual int method2() { return 2; } // not overridden
};

struct Derived : public Base
{
    virtual int method1() { return 3; }
}

int main()
{
    Base* b = new Derived;
    std::cout << b->method1() << std::endl;
}

sẽ in 3, bởi vì phương thức đã được gửi động . Tiêu chuẩn C ++ rất cẩn thận không chỉ định chính xác điều này xảy ra như thế nào ở hậu trường, nhưng mọi trình biên dịch dưới ánh nắng mặt trời đều thực hiện theo cùng một cách. Họ tạo một bảng con trỏ hàm cho mỗi kiểu đa hình (được gọi là bảng ảo hoặc vtable ) và khi bạn gọi một phương thức ảo, phương thức "thực" sẽ được tra cứu từ vtable và phiên bản đó được gọi. Vì vậy, bạn có thể hình ảnh một cái gì đó giống như mã giả này:

struct BaseVTable
{
    int (*_method1) () = &Base::method1; // real function address
    int (*_method2) () = &Base::method2;
};

struct DerivedVTable
{  
    int (*method) () = &Derived::method1;
    int (*method2) () = &Base::method2; // not overridden
};

Bằng cách này, trình biên dịch có thể chắc chắn rằng một phương thức với một chữ ký cụ thể tồn tại tại thời điểm biên dịch. Tuy nhiên, tại thời điểm chạy, cuộc gọi thực sự có thể được gửi qua vtable đến một chức năng khác. Các cuộc gọi đến các chức năng ảo chậm hơn một chút so với các cuộc gọi không phải ảo, do có thêm bước chuyển hướng.


Mặt khác, hiểu biết của tôi về thuật ngữ ràng buộc trễ là con trỏ hàm được tra cứu theo tên trong thời gian chạy, từ bảng băm hoặc một cái gì đó tương tự. Đây là cách mọi thứ được thực hiện bằng Python, JavaScript và (nếu bộ nhớ phục vụ) Objective-C. Điều này giúp bạn có thể thêm các phương thức mới vào một lớp trong thời gian chạy , điều này không thể thực hiện trực tiếp trong C ++. Điều này đặc biệt hữu ích để triển khai những thứ như mixin. Tuy nhiên, nhược điểm là tra cứu thời gian chạy thường chậm hơn đáng kể so với ngay cả một cuộc gọi ảo trong C ++ và trình biên dịch không thể thực hiện bất kỳ kiểm tra kiểu thời gian biên dịch nào cho các phương thức mới được thêm vào.


0

Tôi cho rằng ý nghĩa là khi bạn có hai lớp B, C kế thừa cùng một lớp cha A. vì vậy, con trỏ của cha (kiểu A) có thể chứa từng loại con trai. Trình biên dịch không thể biết kiểu giữ trong con trỏ trong thời gian nhất định, vì nó có thể thay đổi trong quá trình chạy chương trình.

Có các chức năng đặc biệt để xác định loại của đối tượng nhất định trong thời gian nhất định. như instanceoftrong java hoặc bằng if(typeid(b) == typeid(A))...c ++.


0

Trong C ++, cả hai dynamic dispatchlate bindingđều giống nhau. Về cơ bản, giá trị của một đối tượng xác định đoạn mã được gọi trong thời gian chạy. Trong các ngôn ngữ như C ++ và java động điều phối là một công văn động cụ thể hơn hoạt động như đã đề cập ở trên. Trong trường hợp này, vì ràng buộc xảy ra trong thời gian chạy, nó cũng được gọi late binding. Các ngôn ngữ như smalltalk cho phép gửi nhiều lần động trong đó phương thức thời gian chạy được chọn trong thời gian chạy dựa trên danh tính hoặc giá trị của nhiều đối tượng.

Trong C ++, chúng ta không thực sự có ràng buộc muộn, bởi vì thông tin kiểu đã được biết. Do đó, trong ngữ cảnh C ++ hoặc Java, điều phối động và liên kết trễ là như nhau. Thực tế / hoàn toàn liên kết trễ, tôi nghĩ là trong các ngôn ngữ như python, là một tra cứu dựa trên phương pháp hơn là dựa trên loại.

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.