การแยกรหัสชั้นเรียนออกเป็นส่วนหัวและไฟล์ cpp


170

ฉันสับสนเกี่ยวกับวิธีการแยกการใช้งานและรหัสการประกาศของคลาสแบบง่ายเป็นส่วนหัวและไฟล์ cpp ใหม่ ตัวอย่างเช่นฉันจะแยกรหัสสำหรับคลาสต่อไปนี้ได้อย่างไร

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y)
  {
    gx = x;
    gy = y;
  }

  int getSum()
  {
    return gx + gy;
  }
};

12
แค่ความคิดเห็นสองสามข้อ: คอนสตรัคเตอร์ควรใช้รายการเริ่มต้นแทนการตั้งค่าสมาชิกในเนื้อความ สำหรับคำอธิบายที่ดีและเข้าใจง่ายโปรดดูที่: codeguru.com/forum/showthread.php?t=464084นอกจากนี้ยังเป็นสถานที่ที่ธรรมดาที่สุดที่จะมีสนามสาธารณะที่ด้านบน มันจะไม่ส่งผลกระทบอะไรเลย แต่เนื่องจากฟิลด์สาธารณะเป็นเอกสารประกอบการเรียนของคุณ
martiert

2
@martiert การมีpublic:สมาชิกที่อยู่ด้านบนอาจมีผลกระทบมากถ้าผู้ใช้ย้ายพวกเขาตามคำแนะนำนี้ - แต่มีการสั่งซื้อการอ้างอิงระหว่างสมาชิกและยังไม่ทราบว่าสมาชิกจะเริ่มต้นในการประกาศ ;-)
underscore_d

1
@underscore_d นั่นเป็นความจริง แต่แล้วอีกครั้งเราทุกคนรวบรวมคำเตือนเป็นข้อผิดพลาดและคำเตือนทั้งหมดที่เราสามารถคิดได้ใช่มั้ย ว่าอย่างน้อยจะบอกคุณว่าคุณกำลังกวดขันขึ้นนี้ แต่ใช่คนใช้วิธีการคำเตือนเล็ก ๆ น้อย ๆ และเพียงไม่สนใจพวกเขา :(
martiert

@ Smartiert จุดที่ดีลืมไปแล้วว่าสร้างคำเตือน - ถ้ามีเพียงคำเตือนที่อ่านมากที่สุด :-) ฉันใช้พวกมันแล้วลองรหัสให้หมด มีบางอย่างที่หลีกเลี่ยงไม่ได้ - ดังนั้นฉันจึงพูดว่า 'ขอบคุณสำหรับคำเตือน แต่ฉันรู้ว่าฉันกำลังทำอะไร!' - แต่ส่วนใหญ่ได้รับการแก้ไขที่ดีที่สุดเพื่อหลีกเลี่ยงความสับสนในภายหลัง
underscore_d

การมีฟิลด์สาธารณะที่ด้านบนเป็นเพียงสไตล์ที่หลายคนยอมรับในความคิดของฉัน นอกจากนี้คุณต้องจำบางสิ่งตามที่ @martiert พูดถึง
Vassilis

คำตอบ:


233

การประกาศคลาสจะเข้าไปในไฟล์ส่วนหัว เป็นสิ่งสำคัญที่คุณจะต้องเพิ่ม#ifndefเจ้าหน้าที่ป้องกันหรือถ้าคุณอยู่บนแพลตฟอร์ม MS คุณสามารถใช้งาน#pragma onceได้เช่นกัน นอกจากนี้ฉันได้ละเว้นส่วนตัวโดยค่าเริ่มต้นสมาชิกคลาส C ++ เป็นส่วนตัว

// A2DD.h
#ifndef A2DD_H
#define A2DD_H

class A2DD
{
  int gx;
  int gy;

public:
  A2DD(int x,int y);
  int getSum();

};

#endif

และการนำไปใช้ในไฟล์ CPP:

// A2DD.cpp
#include "A2DD.h"

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

53
โปรดจำไว้ว่าหากคุณกำลังเขียนโปรแกรมแม่แบบคุณจะต้องเก็บทุกอย่างไว้ในไฟล์. h เพื่อให้คอมไพเลอร์จะยกตัวอย่างรหัสที่ถูกต้องในขณะทำการรวบรวม
linello

2
คุณมี#ifndefสิ่งของในส่วนหัวหรือไม่
Ferenc Deak

4
ดังนั้นหมายความว่าไฟล์ทั้งหมดที่มีไฟล์ส่วนหัวของคุณจะ "เห็น" สมาชิกส่วนตัว ถ้าเช่นคุณต้องการเผยแพร่ lib และส่วนหัวของมันคุณต้องแสดงสมาชิกส่วนตัวของชั้น?
Gauthier

1
ไม่มีสำนวนการใช้งานส่วนตัวที่ยอดเยี่ยม: en.wikipedia.org/wiki/Opaque_pointerคุณสามารถใช้เพื่อซ่อนรายละเอียดการใช้งานได้
Ferenc Deak

3
nitpick เล็กน้อยที่มีข้อความ: "การประกาศคลาสจะเข้าไปในไฟล์ส่วนหัว" นี่คือการประกาศจริง ๆ แต่ก็เป็นคำจำกัดความด้วยเช่นกัน แต่ในภายหลังมีคำจำกัดความเก่าที่ฉันอยากจะบอกว่านิยามของคลาสเข้าสู่ไฟล์ส่วนหัว ในหน่วยการแปลคุณมีคำจำกัดความของฟังก์ชั่นสมาชิกไม่ใช่คำจำกัดความของคลาส ฉันเห็นด้วยนี่อาจจะคุ้มค่าการแก้ไขเล็กน้อย?
lubgr

17

โดยทั่วไปแล้ว. h ของคุณจะมีคลาส defition ซึ่งเป็นข้อมูลทั้งหมดของคุณและการประกาศเมธอดทั้งหมดของคุณ เช่นนี้ในกรณีของคุณ:

A2DD.h:

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y);    
  int getSum();
};

และ. cpp ของคุณจะมีวิธีการใช้งานดังนี้:

A2DD.cpp:

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

7

สิ่งสำคัญคือให้ผู้อ่านสะดุดกับคำถามนี้เมื่อทำการวิจัยในหัวข้อที่กว้างขึ้นว่าไม่จำเป็นต้องใช้กระบวนการคำตอบที่ได้รับการยอมรับในกรณีที่คุณต้องการแยกโครงการของคุณออกเป็นไฟล์ มันจำเป็นเฉพาะเมื่อคุณต้องการการใช้งานหลายชั้นเดียว ถ้าการใช้งานต่อคลาสเป็นหนึ่งไฟล์เพียงไฟล์เดียวสำหรับแต่ละไฟล์ก็เพียงพอ

ดังนั้นจากตัวอย่างของคำตอบที่ยอมรับจะมีเพียงส่วนนี้เท่านั้น:

#ifndef MYHEADER_H
#define MYHEADER_H

//Class goes here, full declaration AND implementation

#endif

นิยามตัวประมวลผลล่วงหน้า #ifndef ฯลฯ อนุญาตให้ใช้ได้หลายครั้ง

PS หัวข้อจะชัดเจนขึ้นเมื่อคุณรู้ว่า C / C ++ คือ 'โง่' และ #include เป็นเพียงวิธีที่จะพูดว่า "ทิ้งข้อความนี้ ณ จุดนี้"


คุณสามารถทำได้โดยใส่ไฟล์ "แยก" ใน.cppหรือเพียงแค่.h"ดี" จริง ๆ สำหรับวิธีการจัดระเบียบโค้ดนี้
Benny Jobigan

1
ฉันคิดว่าบางโปรเจ็กต์จะแยกส่วนหัวและไฟล์การนำไปใช้ (เดี่ยว) เพื่อให้สามารถแจกจ่ายไฟล์ส่วนหัวได้อย่างง่ายดายโดยไม่ต้องเปิดเผยซอร์สโค้ดของการใช้งาน
Carl G

ฉันดีใจที่คุณชี้ให้เห็นเพราะตอนแรกฉันเรียนรู้จาก C ++ แล้วเปลี่ยนเป็น C # เมื่อหลายปีก่อนและเมื่อไม่นานมานี้ได้ทำ C ++ มากอีกครั้งและฉันลืมไปว่าการแยกไฟล์ที่น่าเบื่อและน่ารำคาญนั้นคืออะไร ส่วนหัวฉันกำลังมองหาคนที่ให้เหตุผลที่ดีที่จะไม่ทำอย่างนั้นเมื่อฉันพบสิ่งนี้ @CarlG มีจุดดี แต่นอกเหนือจากสถานการณ์ที่ฉันคิดว่าทำแบบอินไลน์ทั้งหมดเป็นวิธีที่จะไป
Peter Moore

6

โดยทั่วไปไวยากรณ์ที่ปรับเปลี่ยนของการประกาศฟังก์ชัน / คำจำกัดความ:

a2dd.h

class A2DD
{
private:
  int gx;
  int gy;

public:
  A2DD(int x,int y);

  int getSum();
};

a2dd.cpp

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

5

A2DD.h

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y);

  int getSum();
};

A2DD.cpp

  A2DD::A2DD(int x,int y)
  {
    gx = x;
    gy = y;
  }

  int A2DD::getSum()
  {
    return gx + gy;
  }

แนวคิดคือการเก็บลายเซ็นของฟังก์ชันและสมาชิกทั้งหมดไว้ในไฟล์ส่วนหัว
สิ่งนี้จะช่วยให้ไฟล์โครงการอื่น ๆ ดูว่าคลาสมีลักษณะอย่างไรโดยไม่ต้องรู้การใช้งาน

และนอกจากนั้นคุณสามารถรวมไฟล์ส่วนหัวอื่น ๆ ในการใช้งานแทนส่วนหัว สิ่งนี้มีความสำคัญเนื่องจากส่วนหัวใดก็ตามที่รวมอยู่ในไฟล์ส่วนหัวของคุณจะถูกรวมไว้ (สืบทอดมา) ในไฟล์อื่นที่มีไฟล์ส่วนหัวของคุณ


4

คุณออกจากการประกาศในไฟล์ส่วนหัว:

class A2DD
{
  private:
  int gx;
  int gy;

  public:
    A2DD(int x,int y); // leave the declarations here
    int getSum();
};

และใส่คำจำกัดความในไฟล์การนำไปใช้งาน

A2DD::A2DD(int x,int y) // prefix the definitions with the class name
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

คุณสามารถผสมทั้งสอง (ทิ้งgetSum()คำจำกัดความในส่วนหัว) สิ่งนี้มีประโยชน์เนื่องจากช่วยให้คอมไพเลอร์มีโอกาสที่ดีขึ้นในการทำอินไลน์ แต่ก็หมายความว่าการเปลี่ยนการใช้งาน (หากปล่อยไว้ในส่วนหัว) อาจทำให้เกิดการสร้างไฟล์อื่น ๆ ทั้งหมดที่มีส่วนหัว

โปรดทราบว่าสำหรับเทมเพลตคุณต้องเก็บทุกอย่างไว้ในส่วนหัว


1
การใส่สมาชิกส่วนตัวและฟังก์ชั่นในไฟล์ส่วนหัวจะไม่ถือว่าเป็นการรั่วไหลของรายละเอียดการใช้งาน?
Jason

1
@ Jason, ประเภทของ สิ่งเหล่านี้เป็นรายละเอียดการปฏิบัติที่จำเป็น เช่นฉันต้องรู้ว่าจะใช้พื้นที่เท่าใดในกองซ้อน การใช้งานฟังก์ชั่นไม่จำเป็นสำหรับหน่วยการรวบรวมอื่น ๆ
Paul Draper

1

โดยปกติคุณจะใส่เฉพาะการประกาศและฟังก์ชั่นอินไลน์สั้น ๆ จริงๆในไฟล์ส่วนหัว:

ตัวอย่างเช่น

class A {
 public:
  A(); // only declaration in the .h unless only a short initialization list is used.

  inline int GetA() const {
    return a_;
  }

  void DoSomethingCoplex(); // only declaration
  private:
   int a_;
 };

0

ฉันจะไม่อ้างอิงตัวอย่างของคุณด้วยเพราะมันค่อนข้างง่ายสำหรับคำตอบทั่วไป (ตัวอย่างเช่นมันไม่มีฟังก์ชัน templated ซึ่งบังคับให้คุณนำไปใช้กับส่วนหัว) สิ่งที่ฉันทำตามกฎง่ายๆคือpimpl สำนวน

มันมีประโยชน์ค่อนข้างมากเมื่อคุณได้รับเวลาในการรวบรวมและน้ำตาล syntactic ที่เร็วขึ้น:

class->member แทน class.member

ข้อเสียเปรียบเพียงอย่างเดียวคือตัวชี้พิเศษที่คุณจ่าย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.