22. 값에 의한 호출보다는 레퍼런스에 의한 호출을 선호한다.
두개의 클래스가 있다고 보고
class Person
{
public:
Person()
{
TRACE("Pserson 생성자\r\n");
};
~Person()
{
TRACE("Pserson 소멸자\r\n");
};
};
class Student : public Person
{
public:
Student()
{
TRACE("%p : student 생성자\r\n", this);
};
Student(Student& a)
{
TRACE("%p : 복사생성자\r\n", this);
};
~Student()
{
TRACE("%p : student 소멸자\r\n", this);
};
};
// 함수
Student returnStudent(Student s)
{ return s; }
// 실행
Student pluto;
returnStudent(pluto);
이과정에서 생기는 오버헤드는?
1. (Student s) // 복사 생성자
2. return s; // 복사생성자, Student s 의 소멸자
3. returnStudent(pluto); // retusn s 의 소멸자
(각각의 Student에 따른 Person 생성자, 소멸자 호출)
대체방안은? > 어떠한 생성자도 소멸자도 호출되지 않는다.
const Student& returnStudent(const Student& s)
{ return s; }
또 다른 예
// 클래스
class Window
{
public :
CString Name() const
{
return m_strName;
}
virtual void Display() const;
CString m_strName;
};
class WindowWithScrollBars: public Window
{
public :
virtual void Display() const;
};
// 1. 객체복사
void printNameAdnDisplay(Window w)
{
TRACE("%s \r\n", w.Name());
w.Display();
}
// 2. 레퍼런스 전달
/*
void printNameAdnDisplay(Window &w)
{
TRACE("%s \r\n", w.Name());
w.Display();
}*/
WindowWithScrollBars wwsb;
printNameAdnDisplay(wwsb);
1. 번과 같은 경우는 w.Display() 가 Window객체의 함수호출
2. 번과 같은 경우는 w.Display() 가 WindowWithScrollBars객체의 함수 호출
예외 ) 레퍼런스에 의해 인자를 전달할 수 없는 경우도 있다 (항목 23)
23. 객체 반환시 레퍼런스를 반환해서는 안되는 경우
1. *(곱하기) 연산과 같은 내부적으로 새로운 값을 만들어내는 경우
2. 1번의 예로 * 연산을 하고 지역변수를 & 통해 리턴하는 문제가 생기며(항목31)
new를 통한 주소 리턴하는 문제는 delete 시점 문제와 중복된 d = a*b*c 와 같은 연산시 delete 할수없는 영역이 생긴다.
예를들면 a*b 를 통해 new로 만들어진 객체가 생기고, 그 객체에 * c 를 해서 d에 값을 넣고 d를 delete할경우
a*b에 대한 delete는 할 수가 없게 된다.
3. static를 통한 접근도 (a*b) == (c*d) 연산시 항상 같은 값을 같는 문제가 발생한다.
24. 함수 오버로딩과 디폴트 인자값 중에서 주의깊게 선택한다.
1. 합리적인 디폴트값을 선택할 수 있고, 한가지 알고리즘만 사용할 경우는 디폴트 인자를 이용한다.
2. 들어오는 인자의 갯수에 따라 바뀌는 로직은 함수오버로딩을 사용한다.(예; 평균구하기)
3. 사용하는 알고리즘이 주어진 입력에 의존할 때 발생한다. (주로 디폴트 생성자, 복사 생성자)
> 디폴트 생성자는 초기값 검사가 필요하지만 복사생성자는 필요없다
25. 포인터나 수치형 타입상의 오버로딩을 피한다.
26. 함수 오버로딩과 디폴트 인자값 중에서 주의깊게 선택한다.
1. A클래스와 B클래스간에 타입캐스팅이 가능하고, 생성자에서 변환해주는 작업.
2. 2개의 상위 클래스에서 상속받았을 경우, 어떤 메쏘드가 호출될지 모호한 상황
27. 의도하지 않은 내부 생성 멤버 함수의 이용을 명시적으로 막는다.
1. 클래스 탬플릿으로서 Array를 작성하려고 한다고 가정하자(경계값 검사외엔 모든면에서
c++ 배열처럼 동작한다고 가정할 경우 객체들간의 치환을 못하게 해야한다. 왜냐하면 아래와 같이 에러발생
double value1[10];
double value2[10];
value1 = value2; // 에러!
2. 방법은 operator= 를 private로 선언하고 함수를 정의하지 않도록 한다.
28. 내부 데이터에 대한 "핸들"을 리턴하는 것을 피해라.
1. 전역 네임스페이스를 분할한다.
예) using namespace xxx; // xxx 심벌내에 모든것을 블럭내에서 유효하게 함
using xxx::BOOK_VERSION // BOOK_VERSION만 이 영역 내에서 유효하가 함.
cout << xxx::BOOK_VERSION // 한번사용하기위해 BOOK_VERSION을 유효하게 함.
29. 내부 데이터에 대한 "핸들"을 리턴하는 것을 피해라.
. 간단히 설명하자면 어떤 클래스가 const 형태로 잡혀있는 문자열이 있다면,
외부 호출에 의해 리턴할때 포인터나 레퍼런스 형태로 리턴하게 되면,
기존 const의 의미가 날라가 버린다는 뜻.
. 해결방법은 포인터나 레퍼런스도 const형태로 리턴하거나 deep-copy형태를 취해야한다.
. 임시객체는 deep copy가 아닐경우 데이타가 없게된다.
예를 들자면)
//String29.h
class String29
{
public:
String29(const char *value = 0);
~String29 ();
// 1. 빠르지만 부정확한 표현 , 2 느리지만 안전한 구현
//operator char*() const;
// 3. 너무 느리거나 잠재적인 메모리 누수가 신경 쓰인다면
operator const char*() const;
// 4. 레퍼런스 형태
char& operator[](int index) const;
private:
char *data;
};
// 5. 임시객체 관련
String29 someFamousAuthor();
//String29.cpp
String29::String29(const char *value)
{
if(value)
{
data = new char[strlen(value)+1];
strcpy(data, value);
}
else
{
data = new char[1];
*data = 0;
}
}
String29::~String29 ()
{
delete [] data;
}
/*
// 1. 빠르지만 부정확한 표현
inline String29::operator char*() const
{
return data;
}
// 2. 느리지만 안전한 구현
inline String29::operator char*() const
{
char *copy = new char[strlen(data)+1];
strcpy(copy, data);
return copy;
}
*/
// 3. 너무 느리거나 잠재적인 메모리 누수가 신경스인다면
inline String29::operator const char*() const
{
return data;
}
// 4. 레퍼런스 관련
char& String29::operator[](int index) const
{
return data[index];
}
// 5. 임시객체 관련
String29 someFamousAuthor()
{
switch(rand()%3) {
case 0:
return "Margaret Mitchell";
case 1:
return "Stephen King";
case 2:
return "Scott Meyers";
}
return "";
}
// testDlg.cpp
void CEffectiveCPlusPlusDlg::OnButton29()
{
const String29 B("Hello World");
// 1, 2
//char *str = B;
//strcpy(str, "Hi, Mom");
// 3
const char *str = B;
//strcpy(str, "Hi, Mom"); // 에러 검출
// 4. 레퍼런스 형태도 날라간다.
String29 s = "I'm not constant";
s[0] = 'x';
const String29 cs = "I'm constant";
cs[0] = 'x'; // x를 엎어 버린다.
// 5. someFamousAuthor();함수에서 가져오는 String이 임시 객체이므로 날라가 버린다.
const char *pc = someFamousAuthor();
TRACE(pc); // 없는 데이터라오류난다.
}
30. 접근하기 어려운 멤버에 대한 비상수 포인터나 레퍼런스를
리턴하는 멤버 함수 사용을 피하라
. pirvate로 선언된 변수를 public함수에서 포인터나 레퍼런스형태로 리턴하는 경우 private
선언한 존재이유가 없어짐.
. 포인터는 데이터 멤버뿐만아니라 멤버함수에 대해서도 주의해야 한다. 왜냐하면
함수에 대한 포인터를 리턴하는 것도 가능하기 때문
예)
class Person;
// PPMF = Person 멤버함수에 대한 포인터
typedef void (Person::*PPMF)();
class Person
{
public:
static PPMF verificationFunction()
{ return &Person::verifyAddress; }
private:
Address address;
void verifyAddress();
}
// 실행문
Person scott;
PPMF pmf = scott.verificationFunction();
(scott.*pmf)() ; // scott.verifyAddress 호출과 동일
[출처] Effective C++ 3 (정리) - 내용 추가중|작성자 후니