Chuyên môn hóa mẫu của một phương thức duy nhất từ ​​một lớp mẫu


92

Luôn xem xét rằng tiêu đề sau, chứa lớp mẫu của tôi, được bao gồm trong ít nhất hai .CPPtệp, mã này biên dịch chính xác:

template <class T>
class TClass 
{
public:
  void doSomething(std::vector<T> * v);
};

template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
  // Do something with a vector of a generic T
}

template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
  // Do something with a vector of int's
}

Nhưng lưu ý nội tuyến trong phương pháp chuyên môn hóa. Cần phải tránh lỗi trình liên kết (trong VS2008 là LNK2005) do phương thức được định nghĩa nhiều hơn một lần. Tôi hiểu điều này vì AFAIK một chuyên môn hóa mẫu đầy đủ giống như một định nghĩa phương pháp đơn giản.

Vì vậy, làm cách nào để loại bỏ điều đó inline? Mã không được trùng lặp trong mỗi lần sử dụng. Tôi đã tìm kiếm trên Google, đọc một số câu hỏi ở đây trong SO và thử nhiều giải pháp được đề xuất nhưng không có giải pháp nào được tạo thành công (ít nhất là không phải trong VS 2008).

Cảm ơn!


4
Tại sao bạn muốn xóa nội dòng? Bạn có thấy nó không hài lòng về mặt thẩm mỹ? Bạn có nghĩ rằng nó thay đổi ý nghĩa của mã của bạn?
Martin York

1
Bởi vì nếu phương pháp này sẽ "dài" và được sử dụng ở nhiều nơi, tôi sẽ bị sao chép mã nhị phân của nó ở khắp mọi nơi, phải không? Tôi cố gắng để giải thích điều này trong câu hỏi nhưng tôi đoán nó là không rõ ràng ... :)
Chuim

@Martin: Điều gì sẽ xảy ra nếu việc triển khai cần nhiều mã khác sau đó phải được bao gồm bởi tiêu đề này thay vì tệp cpp?
sbi

Câu trả lời:


71

Như với các hàm đơn giản, bạn có thể sử dụng khai báo và thực hiện. Đưa vào khai báo tiêu đề của bạn:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

và đưa việc triển khai vào một trong các tệp cpp của bạn:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

Đừng quên loại bỏ nội tuyến (tôi đã quên và nghĩ rằng giải pháp này sẽ không hoạt động :)). Đã kiểm tra trên VC ++ 2005


Tôi đã thử một cái gì đó ít nhất là tương tự như điều này trước đây nhưng tôi gặp các lỗi khác nhưng bây giờ bạn đã đề cập, tôi hẳn đã quên loại bỏ inlinebản sao / dán trong khi. Cách này đã hoạt động!
Chuim

Điều tương tự cũng áp dụng cho các hàm không có khuôn mẫu (trái ngược với các phương thức lớp). Tôi đã nhận được cùng một lỗi trình liên kết cho chuyên môn hóa chức năng của mình. Tôi đã chuyển phần nội dung của chuyên môn hóa vào một tệp .cpp và để phần khai báo của chuyên môn hóa trong tiêu đề và mọi thứ đều hoạt động. Cảm ơn!
aldo

Tôi chỉ gặp phải vấn đề này, và ở trên đã giải quyết nó cho tôi. Ngoài ra, bạn cần quan tâm đến nơi trình biên dịch mở rộng mã mẫu. Nếu nó được thực hiện hai lần, trình biên dịch sẽ phàn nàn về nhiều định nghĩa.
Diederik

4

Bạn cần chuyển định nghĩa chuyên môn hóa sang tệp CPP. Cho phép chuyên môn hóa hàm thành viên của lớp mẫu ngay cả khi hàm không được khai báo là mẫu.


3

Không có lý do gì để xóa nội dòng từ khóa.
Dù sao thì nó cũng không thay đổi ý nghĩa của mã.


Sao chép từ bình luận câu hỏi: Bởi vì nếu phương pháp này sẽ "dài" và được sử dụng ở nhiều nơi, tôi sẽ nhận được mã nhị phân của nó được sao chép khắp nơi, phải không? Tôi cố gắng để giải thích điều này trong câu hỏi nhưng tôi đoán nó là không rõ ràng ... :)
Chuim

1
Không. Trình liên kết loại bỏ mọi bản sao thừa. Vì vậy, trong một ứng dụng hoặc lib, bạn sẽ chỉ có một phiên bản của phương thức.
Martin York

3
Nếu inlinetừ khóa dẫn đến hàm thực sự được nội tuyến (tiêu chuẩn nói rằng trình biên dịch nên coi nó như một gợi ý), thì những bản sao thừa đó không thể bị xóa. Đó là, tuy nhiên, chỉ một gợi ý để inline (tác dụng chính của nó là để nói "không tạo ra lỗi trên va chạm liên kết một cách đặc biệt")
Yakk - Adam Nevraumont

2

Nếu bạn muốn loại bỏ nội tuyến vì bất kỳ lý do gì, giải pháp của Maximum1000 là hoàn toàn hợp lệ.

Tuy nhiên, trong nhận xét của bạn, có vẻ như bạn tin rằng từ khóa nội tuyến có nghĩa là hàm với tất cả nội dung của nó luôn được nội tuyến nhưng AFAIK thực sự phụ thuộc rất nhiều vào việc tối ưu hóa trình biên dịch của bạn.

Trích dẫn từ C ++ FAQ

Có một số cách để chỉ định rằng một hàm là nội tuyến, một số cách trong số đó liên quan đến từ khóa nội tuyến, những cách khác thì không. Bất kể bạn chỉ định một hàm là nội tuyến như thế nào, đó là một yêu cầu mà trình biên dịch được phép bỏ qua: trình biên dịch có thể mở rộng nội tuyến một số, tất cả hoặc không có vị trí nào mà bạn gọi một hàm được chỉ định là nội tuyến. (Đừng nản lòng nếu điều đó có vẻ mơ hồ vô vọng. Tính linh hoạt của những điều trên thực sự là một lợi thế lớn: nó cho phép trình biên dịch xử lý các chức năng lớn khác với các chức năng nhỏ, ngoài ra nó còn cho phép trình biên dịch tạo mã dễ gỡ lỗi nếu bạn chọn các tùy chọn trình biên dịch phù hợp.)

Vì vậy, trừ khi bạn biết rằng chức năng đó sẽ thực sự mở rộng tệp thực thi của bạn hoặc trừ khi bạn muốn xóa nó khỏi tiêu đề định nghĩa mẫu vì những lý do khác, bạn thực sự có thể để nó ở vị trí cũ mà không gây hại gì.


1

Tôi muốn nói thêm rằng vẫn có lý do chính đáng để giữ inlinetừ khóa ở đó nếu bạn định bỏ luôn chuyên môn hóa trong tệp tiêu đề.

"Theo trực giác, khi bạn hoàn toàn chuyên môn hóa một thứ gì đó, nó sẽ không phụ thuộc vào thông số mẫu nữa - vì vậy trừ khi bạn thực hiện nội tuyến chuyên môn hóa, bạn cần đặt nó vào tệp .cpp thay vì .h, nếu không bạn sẽ vi phạm quy tắc một định nghĩa ... "

Tham khảo: https://stackoverflow.com/a/4445772/1294184


0

Đây là một OT nhỏ, nhưng tôi nghĩ tôi sẽ để nó ở đây trong trường hợp nó giúp ích cho người khác. Tôi đã tìm kiếm trên Google về chuyên môn hóa mẫu đã dẫn tôi đến đây, và mặc dù câu trả lời của @ maxim1000 là đúng và cuối cùng đã giúp tôi tìm ra các vấn đề của mình, tôi không nghĩ rằng nó quá rõ ràng.

Tình huống của tôi hơi khác một chút (nhưng đủ tương tự để tôi nghĩ câu trả lời này) so với OP. Về cơ bản, tôi đang sử dụng thư viện của bên thứ ba với tất cả các loại lớp khác nhau xác định "loại trạng thái". Trọng tâm của các kiểu này chỉ đơn giản là enums, nhưng các lớp đều kế thừa từ một lớp cha chung (trừu tượng) và cung cấp các hàm tiện ích khác nhau, chẳng hạn như nạp chồng toán tử và một static toString(enum type)hàm. Mỗi trạng thái enumlà khác nhau và không liên quan. Ví dụ, một cái enumcó các trường NORMAL, DEGRADED, INOPERABLE, một cái khác có AVAILBLE, PENDING, MISSING, v.v ... Phần mềm của tôi phụ trách quản lý các loại trạng thái khác nhau cho các thành phần khác nhau. Nó đến từ việc tôi muốn sử dụng các toStringchức năng choenumcác lớp, nhưng vì chúng trừu tượng nên tôi không thể khởi tạo chúng trực tiếp. Tôi có thể đã mở rộng từng lớp mà tôi muốn sử dụng, nhưng cuối cùng tôi quyết định tạo một templatelớp, nơi typenamesẽ là bất kỳ trạng thái cụ thể nào enummà tôi quan tâm. Có lẽ có thể có một số tranh luận về quyết định đó, nhưng tôi cảm thấy rằng đó là công việc ít hơn nhiều so với việc mở rộng mỗi enumlớp trừu tượng với một lớp tùy chỉnh của riêng tôi và triển khai các hàm trừu tượng. Và tất nhiên trong mã của tôi, tôi chỉ muốn có thể gọi .toString(enum type)và để nó in ra biểu diễn chuỗi của điều đó enum. Vì tất cả những thứ enumhoàn toàn không liên quan đến nhau, chúng đều cótoString hàm mà (sau một số nghiên cứu tôi đã học được) phải được gọi bằng cách sử dụng chuyên môn hóa mẫu. Điều đó đã dẫn tôi đến đây. Dưới đây là MCVE về những gì tôi phải làm để làm cho việc này hoạt động chính xác. Và thực sự giải pháp của tôi hơi khác so với @ maxim1000.

Đây là một tệp tiêu đề (được đơn giản hóa rất nhiều) cho enums. Trong thực tế, mỗi enumlớp được định nghĩa trong tệp riêng của nó. Tệp này đại diện cho các tệp tiêu đề được cung cấp cho tôi như một phần của thư viện mà tôi đang sử dụng:

// file enums.h
#include <string>

class Enum1
{
public:
  enum EnumerationItem
  {
    BEARS1,
    BEARS2,
    BEARS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

class Enum2
{
public:
  enum EnumerationItem
  {
    TIGERS1,
    TIGERS2,
    TIGERS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

thêm dòng này chỉ để tách tệp tiếp theo thành một khối mã khác:

// file TemplateExample.h
#include <string>

template <typename T>
class TemplateExample
{
public:
  TemplateExample(T t);
  virtual ~TemplateExample();

  // this is the function I was most concerned about. Unlike @maxim1000's
  // answer where (s)he declared it outside the class with full template
  // parameters, I was able to keep mine declared in the class just like
  // this
  std::string toString();

private:
  T type_;
};

template <typename T>
TemplateExample<T>::TemplateExample(T t)
  : type_(t)
{

}

template <typename T>
TemplateExample<T>::~TemplateExample()
{

}

tập tin tiếp theo

// file TemplateExample.cpp
#include <string>

#include "enums.h"
#include "TemplateExample.h"

// for each enum type, I specify a different toString method, and the
// correct one gets called when I call it on that type.
template <>
std::string TemplateExample<Enum1::EnumerationItem>::toString()
{
  return Enum1::toString(type_);
}

template <>
std::string TemplateExample<Enum2::EnumerationItem>::toString()
{
  return Enum2::toString(type_);
}

tập tin tiếp theo

// and finally, main.cpp
#include <iostream>
#include "TemplateExample.h"
#include "enums.h"

int main()
{
  TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1);
  TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3);

  std::cout << t1.toString() << std::endl;
  std::cout << t2.toString() << std::endl;

  return 0;
}

và kết quả này là:

BEARS1
TIGERS3

Không có manh mối nào nếu đây là giải pháp lý tưởng để giải quyết vấn đề của tôi, nhưng nó đã hiệu quả với tôi. Bây giờ, bất kể tôi sử dụng bao nhiêu kiểu liệt kê, tất cả những gì tôi phải làm là thêm một vài dòng cho toStringphương thức trong tệp .cpp và tôi có thể sử dụng toStringphương thức đã được xác định sẵn trong thư viện mà không cần tự triển khai nó và không cần mở rộng từng enumlớp tôi muốn sử dụng.

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.