Trong bài – Đặt tên bị xung đột và giới thiệu về không gian tên(namespace), chúng ta đã giới thiệu khái niệm đặt tên bị xung đột và không gian tên. Xin nhắc lại, xung đột đặt tên xảy ra khi hai số nhận dạng giống nhau được đưa vào cùng một phạm vi và trình biên dịch không thể phân biệt cái nào sẽ sử dụng. Khi điều này xảy ra, trình biên dịch hoặc trình liên kết sẽ tạo ra lỗi vì chúng không có đủ thông tin để giải quyết sự mơ hồ. Khi các chương trình trở nên lớn hơn, số lượng định danh tăng tuyến tính, do đó làm cho xác suất xảy ra xung đột đặt tên tăng theo cấp số nhân.

Hãy xem lại một ví dụ về xung đột đặt tên và sau đó chỉ ra cách chúng ta có thể giải quyết nó bằng cách sử dụng không gian tên. Trong ví dụ sau, foo.cpp và goo.cpp là các file nguồn chứa các hàm thực hiện những việc khác nhau nhưng có cùng tên và tham số.

foo.cpp:

// This doSomething() adds the value of its parameters
int doSomething(int x, int y)
{
    return x + y;
}

goo.cpp:

// This doSomething() subtracts the value of its parameters
int doSomething(int x, int y)
{
    return x - y;
}

main.cpp:

#include <iostream>
 
int doSomething(int x, int y); // forward declaration for doSomething
 
int main()
{
    std::cout << doSomething(4, 3) << '\n'; // which doSomething will we get?
    return 0;
}

Nếu dự án này chỉ chứa foo.cpp hoặc goo.cpp (chứ không phải cả hai), nó sẽ biên dịch và chạy mà không xảy ra sự cố. Tuy nhiên, bằng cách biên dịch cả hai vào cùng một chương trình, bây giờ chúng ta đã đưa hai hàm khác nhau có cùng tên và tham số vào cùng một phạm vi (phạm vi toàn cục), điều này gây ra xung đột đặt tên. Do đó, trình liên kết sẽ gặp lỗi:

goo.cpp:3: multiple definition of `doSomething(int, int)'; foo.cpp:3: first defined here

Lưu ý rằng lỗi này xảy ra tại thời điểm định nghĩa lại, vì vậy việc hàm doSomething có được gọi hay không cũng không thành vấn đề.

Một cách để giải quyết vấn đề này là đổi tên một trong các hàm để các tên không còn xung đột với nhau. Nhưng điều này cũng sẽ yêu cầu thay đổi tên của tất cả các lệnh gọi hàm, điều này có thể gây khó khăn và có thể bị lỗi. Một cách tốt hơn để tránh va chạm là đặt các hàm của bạn vào không gian tên của riêng bạn. Vì lý do này, thư viện chuẩn đã được chuyển vào không gian tên std.

1. Định nghĩa không gian tên của riêng bạn

C ++ cho phép chúng ta xác định không gian tên của riêng mình thông qua từ khóa namespace. Không gian tên mà bạn tạo cho khai báo của riêng mình được gọi là không gian tên do người dùng xác định. Không gian tên được cung cấp bởi C ++ (chẳng hạn như global namespace) hoặc bởi các thư viện (chẳng hạn như namespace std) không được coi là không gian tên do người dùng xác định.

2. Định danh không gian tên thường không viết hoa.

Đây là một ví dụ về các file trong ví dụ trước được viết lại bằng cách sử dụng không gian tên:

foo.cpp:

namespace foo // define a namespace named foo
{
    // This doSomething() belongs to namespace foo
    int doSomething(int x, int y)
    {
        return x + y;
    }
}

goo.cpp:

namespace goo // define a namespace named goo
{
    // This doSomething() belongs to namespace goo
    int doSomething(int x, int y)
    {
        return x - y;
    }
}

Bây giờ doSomething () bên trong foo.cpp nằm bên trong không gian tên foo và doSomething () bên trong goo.cpp nằm bên trong không gian tên goo. Hãy xem điều gì sẽ xảy ra khi chúng ta biên dịch lại chương trình của mình.

main.cpp:

int doSomething(int x, int y); // forward declaration for doSomething
 
int main()
{
    std::cout << doSomething(4, 3) << '\n'; // which doSomething will we get?
    return 0;
}

Câu trả lời là bây giờ chúng ta gặp một lỗi khác!

ConsoleApplication1.obj : error LNK2019: unresolved external symbol "int __cdecl doSomething(int,int)" (?doSomething@@YAHHH@Z) referenced in function _main

Trong trường hợp này, trình biên dịch đã hài lòng (theo khai báo phía trước của chúng ta), nhưng trình liên kết không thể tìm thấy định nghĩa cho doSomething trong không gian tên chung. Điều này là do cả hai phiên bản doSomething của chúng ta không còn nằm trong vùng tên chung nữa!

Có hai cách khác nhau để thông báo cho trình biên dịch biết nên sử dụng phiên bản doSomething () nào, thông qua toán tử phân giải phạm vi hoặc thông qua sử dụng các câu lệnh (chúng ta sẽ thảo luận trong bài học sau của chương này).

Đối với các ví dụ tiếp theo, chúng ta sẽ thu gọn các ví dụ của chúng ta thành giải pháp một file để dễ đọc.

3. Truy cập không gian tên bằng toán tử phân giải phạm vi (: 🙂

Cách tốt nhất để yêu cầu trình biên dịch tìm kiếm trong một không gian tên cụ thể cho một định danh là sử dụng toán tử phân giải phạm vi (: :). Toán tử phân giải phạm vi cho trình biên dịch biết rằng số nhận dạng được chỉ định bởi toán hạng bên phải sẽ được tìm kiếm trong phạm vi của toán hạng bên trái.

Đây là một ví dụ về việc sử dụng toán tử phân giải phạm vi để cho trình biên dịch biết rằng chúng tôi muốn sử dụng phiên bản doSomething () một cách rõ ràng trong không gian tên foo:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <iostream>
 
namespace foo // define a namespace named foo
{
    // This doSomething() belongs to namespace foo
    int doSomething(int x, int y)
    {
        return x + y;
    }
}
 
namespace goo // define a namespace named goo
{
    // This doSomething() belongs to namespace goo
    int doSomething(int x, int y)
    {
        return x - y;
    }
}
 
int main()
{
    std::cout << foo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace foo
    return 0;
}

Điều này tạo ra kết quả mong đợi:

7

Nếu chúng ta muốn sử dụng phiên bản doSomething () có trong goo để thay thế:

#include <iostream>
 
namespace foo // define a namespace named foo
{
    // This doSomething() belongs to namespace foo
    int doSomething(int x, int y)
    {
        return x + y;
    }
}
 
namespace goo // define a namespace named goo
{
    // This doSomething() belongs to namespace goo
    int doSomething(int x, int y)
    {
        return x - y;
    }
}
 
int main()
{
    std::cout << goo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace goo
    return 0;
}

Điều này tạo ra kết quả:

1

Toán tử phân giải phạm vi rất tuyệt vì nó cho phép chúng tôi chọn rõ ràng không gian tên mà chúng ta muốn xem xét, do đó, không có sự mơ hồ tiềm ẩn. Chúng tôi thậm chí có thể làm như sau:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <iostream>
 
namespace foo // define a namespace named foo
{
    // This doSomething() belongs to namespace foo
    int doSomething(int x, int y)
    {
        return x + y;
    }
}
 
namespace goo // define a namespace named goo
{
    // This doSomething() belongs to namespace goo
    int doSomething(int x, int y)
    {
        return x - y;
    }
}
 
int main()
{
    std::cout << foo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace foo
    std::cout << goo::doSomething(4, 3) << '\n'; // use the doSomething() that exists in namespace goo
    return 0;
}

Điều này tạo ra kết quả:

7
1

5. Phạm vi khi không có tiền tố

Toán tử phân giải phạm vi cũng có thể được sử dụng mà không có bất kỳ không gian tên nào trước đó (ví dụ. :: doSomething). Trong trường hợp như vậy, định danh (ví dụ: doSomething) được tìm kiếm trong không gian tên chung.

#include <iostream>
 
void print() // this print lives in the global namespace
{
	std::cout << " there\n";
}
 
namespace foo
{
	void print() // this print lives in the foo namespace
	{
		std::cout << "Hello";
	}
}
 
int main()
{
	foo::print(); // call foo::print()
	::print(); // call print() in global namespace (same as just calling print() in this case)
 
	return 0;
}

Vậy tại sao bạn thực sự muốn làm điều này? Trong hầu hết các trường hợp, bạn sẽ không. Nhưng chúng ta sẽ thấy những ví dụ mà điều này trở nên hữu ích trong các bài học trong tương lai.

6. Cho phép nhiều khối không gian tên

Việc khai báo các khối không gian tên ở nhiều vị trí là hợp pháp (trên nhiều file hoặc nhiều vị trí trong cùng một file). Tất cả các khai báo trong không gian tên được coi là một phần của không gian tên.

circle.h:

#ifndef CIRCLE_H
#define CIRCLE_H
 
namespace basicMath
{
    inline constexpr double pi{ 3.14 };
}
 
#endif

growth.h:

#ifndef GROWTH_H
#define GROWTH_H
 
namespace basicMath
{
    // the constant e is also part of namespace basicMath
    inline constexpr double e{ 2.7 };
}
 
#endif

main.cpp:

#include "circle.h" // for basicMath::pi
#include "growth.h" // for basicMath::e
 
#include <iostream>
 
int main()
{
    std::cout << basicMath::pi << '\n';
    std::cout << basicMath::e << '\n';
 
    return 0;
}

Điều này hoạt động chính xác như bạn mong đợi:

3.14

2.7

Thư viện chuẩn sử dụng rộng rãi tính năng này, vì mỗi file tiêu đề thư viện chuẩn chứa các khai báo của nó bên trong khối std không gian tên chứa trong file header đó. Nếu không, toàn bộ thư viện chuẩn sẽ phải được xác định trong một file header duy nhất!

Lưu ý rằng khả năng này cũng có nghĩa là bạn có thể thêm hàm của riêng mình vào không gian tên std. Làm như vậy thường gây ra hành vi không xác định, vì không gian tên std có một quy tắc đặc biệt, cấm tiện ích mở rộng khỏi code người dùng.

Lưu ý – Không thêm hàm tùy chỉnh vào không gian tên std.

Khi bạn tách code của mình thành nhiều file, bạn sẽ phải sử dụng không gian tên trong tiêu đề và file nguồn.

add.h

#ifndef ADD_H
#define ADD_H
 
namespace basicMath
{
    // function add() is part of namespace basicMath
    int add(int x, int y);
}
 
#endif

add.cpp

#include "add.h"
 
namespace basicMath
{
    // define the function add()
    int add(int x, int y)
    {
        return x + y;
    }
}

main.cpp

#include "add.h" // for basicMath::add()
 
#include <iostream>
 
int main()
{
    std::cout << basicMath::add(4, 3) << '\n';
 
    return 0;
}

Nếu không gian tên bị bỏ qua trong file nguồn, trình liên kết sẽ không tìm thấy định nghĩa về basicMath :: add, vì file nguồn chỉ định nghĩa thêm (không gian tên chung). Nếu không gian tên bị bỏ qua trong file tiêu đề, “main.cpp” sẽ không thể sử dụng basicMath :: add, vì nó chỉ thấy một khai báo cho add (không gian tên chung).

7. Không gian tên lồng nhau

Không gian tên có thể được lồng vào bên trong các không gian tên khác. Ví dụ:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <iostream>
 
namespace foo
{
    namespace goo // goo is a namespace inside the foo namespace
    {
        int add(int x, int y)
        {
            return x + y;
        }
    }
}
 
int main()
{
    std::cout << foo::goo::add(1, 2) << '\n';
    return 0;
}

Lưu ý rằng vì không gian tên goo nằm bên trong foo không gian tên, chúng ta truy cập add dưới dạng foo :: goo :: add.

Trong C ++ 17, các không gian tên lồng nhau cũng có thể được khai báo theo cách này:

#include <iostream>
 
namespace foo::goo // goo is a namespace inside the foo namespace (C++17 style)
{
  int add(int x, int y)
  {
    return x + y;
  }
}
 
int main()
{
    std::cout << foo::goo::add(1, 2) << '\n';
    return 0;
}

8. Bí danh cho không gian tên

Vì việc nhập tên đủ điều kiện của một biến hoặc hàm bên trong một không gian tên lồng nhau có thể gây khó khăn, C ++ cho phép bạn tạo bí danh không gian tên, cho phép chúng ta tạm thời rút ngắn một chuỗi dài các không gian tên thành một thứ gì đó ngắn hơn:

#include <iostream>
 
namespace foo
{
	namespace goo
	{
	        int add(int x, int y)
        	{
	            return x + y;
        	}
	}
}
 
int main()
{
	namespace boo = foo::goo; // boo now refers to foo::goo
 
	std::cout << boo::add(1, 2) << '\n'; // This is really foo::goo::add()
 
	return 0;
} // The boo alias ends here

Một lợi thế tuyệt vời của bí danh không gian tên: Nếu bạn muốn di chuyển hàm trong foo :: goo sang một nơi khác, bạn chỉ có thể cập nhật bí danh boo để phản ánh điểm đến mới, thay vì phải tìm / thay thế mọi phiên bản của foo: : goo.

Cần lưu ý rằng không gian tên trong C ++ ban đầu không được thiết kế như một cách để triển khai hệ thống phân cấp thông tin – chúng được thiết kế chủ yếu như một cơ chế để ngăn chặn xung đột đặt tên. Để làm bằng chứng cho điều này, hãy lưu ý rằng toàn bộ thư viện chuẩn nằm dưới không gian tên số ít std :: (với một số không gian tên lồng nhau được sử dụng cho các tính năng thư viện mới hơn). Một số ngôn ngữ mới hơn (chẳng hạn như C #) khác với C ++ về mặt này.

Nói chung, bạn nên tránh các không gian tên lồng nhau sâu quá.

9.Khi nào bạn nên sử dụng không gian tên

Trong các ứng dụng, không gian tên có thể được sử dụng để tách code dành riêng cho ứng dụng khỏi mã có thể được sử dụng lại sau này (ví dụ: các hàm toán học). Ví dụ: các hàm vật lý và toán học có thể đi vào một không gian tên (ví dụ: math: :). Các chức năng ngôn ngữ và bản địa hóa trong một ngôn ngữ khác (ví dụ: lang: :).

Khi bạn viết một thư viện hoặc code mà bạn muốn phân phối cho người khác, hãy luôn đặt mã của bạn bên trong một không gian tên. code mà thư viện của bạn được sử dụng có thể không tuân theo các phương pháp hay nhất – trong trường hợp như vậy, nếu phần khai báo của thư viện của bạn không nằm trong không gian tên, thì sẽ có nhiều khả năng xảy ra xung đột đặt tên. Như một lợi thế bổ sung, việc đặt code thư viện bên trong không gian tên cũng cho phép người dùng xem nội dung thư viện của bạn bằng cách sử dụng tính năng tự động hoàn thành và đề xuất của trình biên tập của họ.

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!