Thay thế cho các biến toàn cục C++

Biến toàn cầu và Singletons thường được coi là xấu. Bob Schmidt tóm tắt một số lựa chọn thay thế

Gần đây, tôi đã đăng một câu hỏi mà tôi nghĩ là đơn giản lên diễn đàn chung của ACCU

Dự án hiện tại của tôi có một số đối tượng được khởi tạo bởi chính và sau đó cần có sẵn để sử dụng bởi các đối tượng khác và các chức năng không phải thành viên, không phải bạn bè [NMNF] ở tất cả các cấp của cây cuộc gọi. Chúng thực sự đại diện cho chức năng toàn cầu, được xác định khi khởi động chương trình, sẽ chỉ có một phiên bản cho mỗi tệp thực thi

Các phương pháp hay nhất hiện tại cho rằng các biến toàn cục là xấu, cũng như các biến đơn lẻ. C++ không hỗ trợ lập trình hướng khía cạnh. Tôi không muốn phải chuyển các đối tượng này xung quanh trong mọi lệnh gọi hàm tạo và hàm không phải thành viên để chúng có sẵn khi nào và ở đâu chúng cần

Trước đây, tôi đã sử dụng một con trỏ toàn cục cho một lớp cơ sở trừu tượng và đã gán con trỏ cho lớp dẫn xuất được khởi tạo cho con trỏ toàn cục này ngay sau khi đối tượng được khởi tạo trong chính. Một cách tiếp cận tương tự là có một lớp sở hữu con trỏ tới lớp dẫn xuất, con trỏ này có thể được truy xuất thông qua một hàm tĩnh. Việc có các toàn cầu là con trỏ tới các lớp cơ sở trừu tượng làm cho các lớp sử dụng toàn cầu dễ kiểm tra, bởi vì một đối tượng thử nghiệm hoặc giả có thể được sử dụng thay cho đối tượng dẫn xuất thực. (Một vấn đề tôi gặp phải với các đối tượng Singleton và Monostate trong ngữ cảnh này là sự ràng buộc trực tiếp với lớp cụ thể. )

Google-fu của tôi đã khiến tôi thất bại trong việc tìm kiếm các giải pháp thay thế. Có rất nhiều người trong thế giới blog sẵn sàng nói rằng việc sử dụng toàn cầu và đơn lẻ là không tốt, nhưng rất ít người đưa ra bất kỳ giải pháp thay thế thiết thực nào. Tôi không phản đối việc bẻ cong hay phá vỡ các quy tắc - có lẽ một trong những lựa chọn 'xấu' đó thực sự là cách tốt nhất. Bất cứ ai có bất cứ đề nghị?

Động lực

Khách hàng hiện tại của tôi có một hệ thống cũ có các thành phần thu thập dữ liệu theo thời gian thực chủ yếu bằng mã C. Tôi đã ủng hộ việc chuyển sang C++, và trong vài năm qua tôi đã viết hai hệ thống con lớn, độc lập trong C++. Giao diện với phần cứng lĩnh vực mới cung cấp cơ hội lý tưởng để bắt đầu sử dụng C++ rộng rãi hơn, với mục tiêu dài hạn là viết lại một số, nếu không muốn nói là tất cả, mã hiện có

Mục tiêu ngắn hạn thực tế hơn. phát triển một khung cơ bản cho các quy trình phù hợp với kiến ​​trúc tổng thể hiện tại của hệ thống và sử dụng lại các thành phần hiện có, gói mã C trong các giao diện C++ khi được yêu cầu hoặc mong muốn. Rất nhiều chức năng C hiện có sẽ vẫn là các chức năng NMNF với liên kết C

Trường hợp cụ thể khiến tôi đặt câu hỏi là hệ thống con liên lạc giữa các quá trình (IPC) hiện có của hệ thống. Các chi tiết không quan trọng (và là độc quyền); . Các lần đọc từ hệ thống con IPC được cách ly với luồng xử lý chính. Các chức năng gửi được gọi từ bất cứ nơi nào chúng cần, trong luồng xử lý chính (để phản hồi dữ liệu nhận được) hoặc trong một hoặc nhiều luồng xử lý dữ liệu từ thiết bị trường

Tôi muốn bọc mã C hiện có trong giao diện C++. Đã có những cuộc thảo luận về việc thay thế sơ đồ IPC hiện tại bằng một thứ khác (chẳng hạn như WCF) và mục tiêu của tôi là tạo ra một giao diện cho phép sử dụng bất kỳ sơ đồ IPC nào thông qua nội xạ phụ thuộc

Diễn đàn thảo luận

Hóa ra câu hỏi không đơn giản như vậy

Tôi nên chỉ ra rằng tôi đã thực hiện rất nhiều nghiên cứu về chủ đề này. Có rất nhiều câu hỏi tương tự ngoài kia và số lượng người có ý kiến ​​dường như không giới hạn. Phần lớn các câu trả lời cho những câu hỏi này chứa các câu trả lời rất giống nhau, tóm lại là 'các biến toàn cầu là xấu, các đơn lẻ là xấu, đừng làm vậy'. OK, tôi đã biết điều đó rồi. Tôi đang cố gắng tìm một giải pháp thay thế C++ thành ngữ, một giải pháp không yêu cầu tôi chuyển một hoặc nhiều tham số cho mọi hàm tạo và hàm NMNF chỉ vì đối tượng tiếp theo được xây dựng hoặc hàm NMNF tiếp theo có thể cần đến nó

Một số câu trả lời đầu tiên cho câu hỏi của tôi có vẻ hợp lý. Chúng có thể được tóm tắt là, vâng, biến toàn cục và singletons rất tệ khi bị lạm dụng, nhưng có một số thời điểm và địa điểm khi chúng giải quyết vấn đề và có thể được sử dụng trong những trường hợp rất cụ thể và hạn chế. (Tôi sẽ gọi những người này là những người thực dụng. ) Một người trả lời đã đề cập đến mẫu Bộ định vị dịch vụ mà tôi không quen thuộc [ Fowler ]. Nó không thực sự giải quyết được vấn đề của tôi, nhưng nó là một công cụ khác trong bộ công cụ.

Không lâu sau đó, quan điểm nghiêm ngặt 'không bao giờ sử dụng' xuất hiện và được củng cố bởi một số ý kiến ​​đồng tình. (Tôi sẽ gọi những người này là những người theo chủ nghĩa thuần túy. ) Sau đó, những người theo chủ nghĩa thực dụng quay trở lại, và một cuộc tranh luận sôi nổi đã được tổ chức. Tôi hài lòng khi đọc các luận điểm và phản bác khi chúng đến; . Như với hầu hết những điều này, các phản hồi bị ngắt quãng và tôi bị bỏ lại với dấu vết email

Điểm và phản điểm

Tôi sẽ không tốn nhiều giấy mực để cố gắng tóm tắt những điểm mà hai bên đưa ra trong cuộc tranh luận này. Những người tốt tham gia vào cuộc tranh luận đã đưa ra các trường hợp của họ và tôi nghi ngờ rằng tôi có thể công bằng cho họ trong một bản tóm tắt ngắn. Nếu bạn quan tâm đến tất cả các chi tiết, bạn có thể đọc toàn bộ chủ đề trực tuyến [ACCU]

Phần tiếp theo là tóm tắt các lập luận được trình bày trong chủ đề, cùng với một số bình luận của riêng tôi. Cảnh báo công bằng - phần lớn, tôi thấy mình ở trại thực dụng

Tham số hóa từ trên (PfA)

Mẫu PfA , do Kevlin Henney giới thiệu trong 'The PfA Papers' [Henney07a ], là giải pháp thay thế duy nhất được trình bày dưới dạng câu trả lời cho . Thật không may, việc triển khai nó là điều mà tôi thực sự không muốn phải làm – chuyển đối tượng tới mọi lớp và mọi chức năng NMNF cần nó hoặc gọi thứ gì đó cần nó. Hiện tại tôi đang làm việc trong một cơ sở mã nơi mà trong một số hệ thống con, hầu hết mọi quy trình đều có cùng một danh sách nhiều tham số và đã trải nghiệm niềm vui khi phải thêm một tham số vào nhiều chữ ký hàm để lấy một phần dữ liệu mà tôi cần .

Mẫu đối tượng bối cảnh

Một đối tượng ngữ cảnh tổng hợp nhiều tham số lặp lại thành một cấu trúc, do đó chỉ cần có một tham số PfA chung được truyền giữa các hàm và lớp [Henney07b ]. Tôi đã sử dụng mẫu này khi tái cấu trúc mã mà không biết đó là mẫu được đặt tên (nhớ lại nhận xét ở trên về dự án hiện tại của tôi);

phụ thuộc được xác định

Một lý do được đưa ra để sử dụng mẫu PfA là nó xác định các phụ thuộc của một lớp hoặc hàm NMNF. Tôi không thấy lý do này hấp dẫn. Không phải tất cả các đối tượng được sử dụng bởi một lớp hoặc một hàm có thể hoặc sẽ được định nghĩa như một tham số. Có nhiều cách khác để chúng ta khai báo rằng mô-đun A (và các lớp và/hoặc chức năng của nó) phụ thuộc vào thứ gì đó từ mô-đun B – bao gồm các tệp và khai báo chuyển tiếp là hai khai báo ngay lập tức xuất hiện trong đầu.

thử nghiệm

Các mẫu Singleton Monostate đều xử lý bê tông . Những người theo chủ nghĩa thuần túy chỉ ra một cách đúng đắn rằng điều này làm cho mã sử dụng các mẫu này khó kiểm tra, bởi vì chức năng được cung cấp bởi các đối tượng không thể bị chế nhạo. Sử dụng PfA , đối số diễn ra, cho phép đối tượng cụ thể được chuyển từ hàm này sang hàm khác như một con trỏ tới lớp trừu tượng, cho phép thay thế mô hình giả . Tôi đồng ý với mục tiêu, nếu không nhất thiết phải thực hiện.

Ngoại lệ cho ghi nhật ký

Ghi nhật ký là một trong những mối quan tâm xuyên suốt mà chương trình định hướng khía cạnh [IEEE ] được thiết kế để giải quyết. Thật không may, C++ không hỗ trợ mô hình hướng khía cạnh. Một số người theo chủ nghĩa thuần túy nói rằng đôi khi họ sẽ tạo ngoại lệ cho các đối tượng ghi nhật ký và sử dụng Singleton hoặc toàn cầu, trong khi những người khác kiên quyết không bao giờ đi theo con đường đó

Thứ tự khởi tạo

Thứ tự các vấn đề khởi tạo có thể phức tạp, đặc biệt với các biến

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
3 và
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
4 trải rộng trên nhiều đơn vị biên dịch. Đây không phải là vấn đề đối với tôi trong các tình huống trước đây khi tôi sử dụng các biến toàn cục hoặc Singleton s. Các loại chức năng hạn chế được cung cấp bởi các đối tượng đó giúp có thể khởi tạo hoặc khởi tạo các đối tượng trong
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
5 , trước khi bất kỳ mã nào khác cần được thực thi.

đa luồng

Singleton s gặp sự cố trong môi trường đa luồng khi sử dụng tính năng khởi tạo chậm. Khả năng phương thức cá thể của Singleton có thể được gọi 'lần đầu tiên' bởi hai luồng đồng thời dẫn đến tình trạng tranh chấp khó chịu. Điều kiện chủng tộc có thể dễ dàng bị loại bỏ bằng cách gọi phương thức cá thể trong

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
5 trước khi sinh ra bất kỳ luồng nào sử dụng nó. Khởi tạo một đối tượng trong
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
5 và sau đó chuyển đối tượng đó bằng cách sử dụng PfA loại bỏ điều kiện chủng tộc theo cách tương tự.

Ngoài trường hợp đó, tôi không thể thấy nơi nào PfA làm cho đa luồng dễ dàng hơn hoặc ít bị lỗi hơn. Một đối tượng được chia sẻ bởi nhiều luồng vẫn phải nhận biết luồng, bất kể phương thức được sử dụng để đưa đối tượng vào luồng là gì.

Sử dụng cin, cout, cerr và guốc

Việc sử dụng các luồng I/O C++ tiêu chuẩn đã được các nhà thực dụng đưa ra như một ví dụ về các đối tượng đại diện cho trạng thái toàn cầu và không được xử lý bằng cách sử dụng PfA . One respondent replied that “ Code using these is untestable ” and in his classes he teaches his students to “ only include

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
8 in the .cpp module where
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
9 is defined and only use the globals there to pass them down the call chain. […] In all other modules only use
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
0 and
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
1 which define the API but not the global objects (or
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
2 or
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
3 ). ”

Phương thức sơ thẩm()

Việc phải gọi một phương thức thể hiện để trả về một con trỏ hoặc một tham chiếu đến một đối tượng, thay vì chỉ khởi tạo đối tượng, là một phần khó xử của Singleton pattern. I don’t think this, by itself, is a sufficient reason to reject the use of the pattern, but it does add to the negative pile.

Giới thiệu Mẫu nguyên mẫu NVI đơn trạng thái

Danh sách từ 1 đến 5 chứa giải pháp ban đầu của tôi cho vấn đề. Trong một đóng góp bổ sung của tôi cho chủ đề diễn đàn, tôi đã gọi nó (với giọng chắc nịch) “… sự giao thoa giữa mẫu Đơn trạng thái và mẫu phương thức mẫu và/hoặc Thành ngữ Giao diện Không ảo của Herb Sutter, với một chút tiểu cảnh được thêm vào cho tốt . ” [ Sutter01 ] Phiên bản được trình bày ở đây được tinh chỉnh từ nỗ lực ban đầu đó và (hy vọng) sửa lỗi chính tả

Liệt kê 1 là một lớp cơ sở trừu tượng đơn giản và Liệt kê 2 hiển thị một lớp bắt nguồn từ lớp cơ sở. Đây là tất cả những thứ tiêu chuẩn. Các ví dụ cực kỳ đơn giản, vì sự phức tạp ở đây sẽ không thêm bất cứ điều gì vào cuộc thảo luận

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
1Danh sách 1
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
Danh sách 2

Liệt kê 3 hiển thị lớp mới. Đặc điểm chính của nó là một cặp hàm tạo – một mặc định và một lấy con trỏ dùng chung đến lớp cơ sở trừu tượng. Hàm tạo nhận tham số được sử dụng để liên kết đối tượng dẫn xuất cụ thể với Bộ chứa Monostate NVI. Hàm tạo mặc định được sử dụng để truy cập vào đối tượng dẫn xuất. Lớp chứa các hàm nội tuyến, không ảo gọi các hàm ảo được xác định bởi giao diện lớp cơ sở trừu tượng. Bởi vì các hàm không ảo là nội tuyến, lệnh gọi hàm sẽ bị trình biên dịch loại bỏ, chỉ còn lại lệnh gọi hàm ảo.

class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
Liệt kê 3

Liệt kê 4 minh họa cách đối tượng cụ thể được tạo và gắn với đối tượng NVI Đơn trạng thái. Liệt kê 5 là một ví dụ về hàm sử dụng các đối tượng được kết hợp. Cuộc gọi đến

class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
4 trong danh sách 5 cuộc gọi
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
5 đối với đối tượng
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
6 được khởi tạo trong
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
5

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
8Danh sách 4
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
9Danh sách 5

Lớp mới này không phải là Singleton ; . Nó trông giống như một Monostate ; .

Tôi thấy giải pháp này có nhiều ưu điểm. Đầu tiên, không giống như PfA , tôi không phải lo lắng về việc truyền thông tin này đi khắp nơi. Thứ hai, giống như mẫu Monostate , đối tượng được truy cập thông qua một hàm tạo tiêu chuẩn. Thứ ba, mẫu nguyên mẫu truy cập các đối tượng thông qua giao diện lớp cơ sở trừu tượng của chúng, giúp dễ dàng giả lập đối tượng để thử nghiệm.

Một nhược điểm của giải pháp này là phải duy trì lớp học thêm. Tôi không coi đây là một bất lợi lớn. Giao diện không được thay đổi thường xuyên. Khi chúng thay đổi, bạn phải sửa đổi tất cả các lớp dẫn xuất để phù hợp với giao diện mới. Trong những trường hợp bình thường, điều này yêu cầu N thay đổi; . Một nhược điểm lớn hơn là thiếu sự hỗ trợ của trình biên dịch cho thấy rằng lớp bổ sung cần phải được thay đổi để đáp ứng với sự thay đổi trong giao diện. Có lẽ, nếu giao diện đang thay đổi, một số mã ở đâu đó sẽ gọi hàm mới hoặc hàm đã sửa đổi, dẫn đến thay đổi hoặc bổ sung cho lớp bổ sung

Nhưng còn khả năng mở rộng thì sao?

Tại một thời điểm trong dự án đã thúc đẩy toàn bộ cuộc thảo luận này, một yêu cầu mới đã được thảo luận. chương trình sẽ sử dụng một đối tượng lớp dẫn xuất để giao tiếp với X và một đối tượng lớp dẫn xuất khác để giao tiếp với Y. Thật trùng hợp, trong quá trình xem xét ban đầu của bài viết này, một trong những người đánh giá đã viết. “Một câu hỏi có thể đáng được thêm vào, nếu Bob chưa liệt kê nó, là liệu thiết kế có cho phép thay đổi trong tương lai hay không; . ” Đó là nếu ai đó đang đọc được suy nghĩ của tôi. ma quái

Yêu cầu đó đã không tồn tại, nhưng câu hỏi làm thế nào điều này có thể được thực hiện vẫn còn. Suy nghĩ đầu tiên của tôi là sử dụng các mẫu, điều này gây ra vấn đề của riêng nó. Tôi không phải là một lập trình viên mẫu giỏi. Hầu hết những gì tôi làm không yêu cầu mức độ chung chung đó, vì vậy các mẫu tôi đã tạo có xu hướng rất đơn giản. Vì vậy, tiết lộ đầy đủ – có khả năng các mẫu được trình bày ở đây không được hình thành đầy đủ theo cách thông thường. (Ví dụ: không có các lớp đặc điểm liên quan. )

Nỗ lực đầu tiên của tôi đối với một giải pháp mẫu được hiển thị trong Liệt kê 6. Phiên bản này cho phép tồn tại nhiều phiên bản của các đối tượng mẫu nguyên mẫu, miễn là các loại được sử dụng trong chuyên môn hóa mẫu là khác nhau. Đó là một điểm yếu – các loại cần phải khác nhau

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
1Liệt kê 6

Điều này dẫn đến đoạn mã trong Liệt kê 7. Tôi không biết đây có phải là thành ngữ hay không, nhưng nó đã hoạt động. Nó trông xấu xí, nhưng tôi thấy hầu hết các mã mẫu ít nhất là hơi kém hấp dẫn. Các

class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
9 ở cuối Liệt kê 7 làm cho việc sử dụng mẫu dễ dàng hơn. (Trong cuộc sống thực, tôi sẽ sử dụng phép liệt kê thay vì những con số kỳ diệu. ) Liệt kê 8 minh họa cách bây giờ chúng ta có thể tạo nhiều đối tượng có cùng loại hoặc khác loại. Liệt kê 9 cho thấy các đối tượng mới được sử dụng như thế nào

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
3Danh sách 7
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
4Danh sách 8
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
5Danh sách 9

Tại thời điểm này, bài báo đã được gửi cho một vòng đánh giá khác. Những người đánh giá đã chỉ ra rằng cách tôi sử dụng số nguyên để chuyên biệt hóa mẫu trên thực tế không phải là thành ngữ. Tôi đã được chỉ dẫn theo hướng gửi thẻ, việc sử dụng các cấu trúc trống với mục đích cung cấp tên an toàn loại làm tham số mẫu. Những người đánh giá cũng khuyến nghị sử dụng

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
80 để tạo đối tượng và một con trỏ dùng chung tới nó trong một bước [Meyers]

Liệt kê 10 cho thấy mẫu lớp được sửa đổi để sử dụng gửi thẻ. Nó có hai tham số mẫu. Đầu tiên thường sẽ là lớp cơ sở trừu tượng. Thứ hai, khi mặc định không được sử dụng, là thẻ cho phép hai đối tượng cùng loại

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
81. Liệt kê 11 chứa các ví dụ về cách tạo ba đối tượng riêng biệt, tương tự như các đối tượng được tạo trong liệt kê 8

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
8Danh sách 10
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
0Danh sách 11

Gói (lại

Giải pháp ban đầu của tôi là thỏa đáng; . (Đây là định dạng của giải pháp được sử dụng trong lần lặp đầu tiên mã sản xuất của khách hàng của tôi. ) Phiên bản mẫu cuối cùng, được gợi ý bởi một yêu cầu bị bỏ rơi và một người đánh giá sắc sảo (cảm ơn bạn), với các tinh chỉnh bổ sung do một số người đánh giá cung cấp, cung cấp một giải pháp linh hoạt hơn

Sự nhìn nhận

Em xin cảm ơn tất cả các bác đã tham gia topic. Theo thứ tự mà bạn đưa ra nhận xét đầu tiên của mình, bạn là. Fernando Cacciola, Anna-Jayne Metcalfe, Alison Lloyd, Balog Pal, Pete Barber, Daire Stockdale, Aaron Ridout, Jonathan Wakely, Russel Winder, Thomas Hawtin, Giovanni Asproni, Martin Moene, Andrew Sutton, Kris, Paul Smith, Peter Sommerlad, và . Nói chung, bạn xứng đáng nhận được tín dụng cho bất cứ điều gì tôi đã nhận ngay trong tháng này. Bất kỳ sai lầm nào tôi mắc phải là của riêng tôi

Như mọi khi, cũng xin cảm ơn Fran và những người đánh giá. Đây là nỗ lực đầu tiên của tôi trong việc viết về một chủ đề kỹ thuật, với mã thực cần biên dịch chính xác, và sự khuyến khích và đóng góp của họ là vô giá. Như Fran đã nêu trong bài báo của cô ấy vào tháng trước, “ (những người đánh giá) có thể đưa ra một số gợi ý […] hoặc các cách làm khác. ” [Buontempo15 ] Tôi chắc chắn đã học được một số cách làm việc mới và tôi rất biết ơn vì điều đó

Cũng xin cảm ơn Michael Chiew và Larry Jankiewicz, những người đã cung cấp phản hồi trong quá trình phát triển ban đầu của ý tưởng này

Một ý kiến ​​​​đối lập

Một nhà phê bình đã chỉ ra rằng giải pháp này vẫn là một giải pháp ngụy trang toàn cầu, với những nhược điểm thông thường (tôi đồng ý). Anh ấy hoặc cô ấy đã đặt câu hỏi tu từ, điều đó có tốt hơn nhiều so với một toàn cầu đơn giản với get/set để thực hiện kiểm tra không?

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
1

Về mặt tích cực, người đánh giá lưu ý rằng giải pháp của tôi cho phép khả năng thay thế và quyền truy cập được kiểm soát tốt hơn so với giải pháp toàn cầu và tiến gần hơn đến việc có một mẫu tạo ra nhiều bản mẫu.

Tôi thấy một vấn đề với cách tiếp cận này là Singleton gặp phải – một cách lấy đối tượng không chuẩn. Trong trường hợp này, đó là một cuộc gọi đến

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
82 , trái ngược với hàm thành viên tĩnh
class mono_nvi
{
public:
  explicit mono_nvi 
    ( std::shared_ptr< abstract_base > p ) 
  {
    if ( p == nullptr )
      throw ( something );
    if ( mp != nullptr )
      throw ( something_else );
    mp = p;
  }
  mono_nvi () 
  {
    if ( mp == nullptr )
      throw ( something );
  }
  inline void foo () 
  {
    mp->foo ();
  }
private:
  static std::shared_ptr< abstract_base > mp;
};
std::shared_ptr< abstract_base > mono_nvi::mp;
			
8 phổ biến đối với Singleton s.

Đính chính

Có lỗi trong bản in bài báo của tôi trong Overload 125, 'I Like Whitespace'. Lỗi được Martin Moene phát hiện khi ông đang chuẩn bị bài báo cho phiên bản trực tuyến. Tôi sẽ để anh ấy mô tả những gì anh ấy tìm thấy (từ email anh ấy gửi cho tôi)

“Là biên tập viên web, tôi đã xem Overload 125 với bài viết của bạn ‘Tôi thích khoảng trắng’. Trong đó, bạn có [ví dụ ở cuối cột bên tay phải trên trang 14] có biểu tượng 'lơ lửng khác'. Đối với tôi, có một sự ngắt kết nối nhận thức trong phiên bản đã sửa giữa tên hàm

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
84 và giá trị của
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
85 mà nó được gọi là
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
86. Tôi. e. phiên bản không giằng làm những gì nó nói, trong khi phiên bản thứ hai thì không. (Trong C và C++,
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
87 được liên kết với
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
88 gần nhất. )”

Martin tất nhiên là đúng. Ví dụ của tôi bị lỗi. Tên của hàm được gọi trong

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
87 lơ lửng lẽ ra phải là
class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
90. Phiên bản trực tuyến của mã đã được sửa chữa. Tôi xin cảm ơn Martin vì đã phát hiện ra lỗi và xuất bản phiên bản đã sửa trực tuyến và Alison Peck vì đã thực hiện thêm công việc cung cấp phiên bản đã sửa cho Martin

Ngoài ra còn có một lỗi đánh máy (vâng, tôi đang mắc lỗi đánh máy) trong biểu thức Boolean phức tạp ở đầu cột bên trái trên trang 14 – thiếu một dấu ngoặc đơn mở trước biểu thức con

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
91. Điều này đã được chỉ ra cho tôi bởi độc giả sắc sảo Jim Dailey, người cũng chia sẻ phong cách ưa thích của mình cho các bài kiểm tra lộn xộn

class concrete_derived : public abstract_base
{
public:
 concrete_derived ()
   : abstract_base ()
 {
 }
 ~concrete_derived ()
 {
 }
 virtual void foo () override
 {
  // DO SOMETHING USEFUL
 }
};
			
2

Tôi cảm ơn Jim đã chỉ ra lỗi của tôi và chia sẻ phong cách của anh ấy

Tôi rất tiếc về các lỗi và bất kỳ sự nhầm lẫn nào mà chúng có thể đã gây ra

Bob

Người giới thiệu

[ACCU] Danh sách gửi thư chung accu, http. // danh sách. accu. org/mailman/private/accu-General/2015-Tháng 1/046003. html

[Buontempo15] Buontempo, Frances, ‘Làm thế nào để viết một bài báo’, Quá tải 125, tháng 2 năm 2015

[Fowler] Fowler, Martin, 'Inversion of Control Containers and the Dependency Injection Pattern', http. //martinfowler. com/bài viết/tiêm. html#UsingAServiceLocator

Tôi có thể sử dụng cái gì thay vì biến toàn cục trong C?

1) Sử dụng biến cục bộ tĩnh trong một hàm . Nó sẽ giữ lại giá trị của nó giữa các lần gọi hàm đó nhưng 'vô hình' đối với phần còn lại của chương trình. 2) Sử dụng biến tĩnh ở phạm vi tệp (nghĩa là được khai báo ngoài tất cả các hàm trong tệp nguồn).

Tôi có thể sử dụng cái gì thay vì toàn cầu?

Sử dụng Biến cục bộ Thay vào đó . Tất cả các tham chiếu đến biến trong ứng dụng có thể được thay thế bằng các lệnh gọi đến setState hoặc getState.

Bạn có nên tránh các biến toàn cầu trong C?

Việc sử dụng các biến toàn cục khiến mã được ghép rất chặt chẽ . Sử dụng các biến toàn cầu gây ô nhiễm không gian tên. Điều này có thể dẫn đến việc chỉ định lại giá trị toàn cầu một cách không cần thiết. Thử nghiệm trong các chương trình sử dụng các biến toàn cục có thể là một nỗi đau lớn vì rất khó để tách rời chúng khi thử nghiệm.

Bạn có nên sử dụng biến toàn cục trong C?

Các biến toàn cục có thể được truy cập bởi tất cả các hàm có trong chương trình. Chỉ cần khai báo một lần. Biến toàn cục rất hữu ích nếu tất cả các hàm đang truy cập cùng một dữ liệu .