«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
03-29 06:50
관리 메뉴

lancelot.com

Pointer to member data 본문

프로그래밍

Pointer to member data

lancelot50 2022. 8. 8. 00:10
  • Pointer to member data
#include<iostream>

struct Point
{
	int x;
	int y;
};

int main()
{
	int num = 0;
	
	int* p1 = &num;

	int Point::* p2 = &Point::y;// 멤버 함수포인터처럼 객체에.* 연산자를 사용해서 접근하기 위함

	*p1 = 10;	// ok
//	*p2 = 10;	// error

	Point pt;
	pt.*p2 = 10;	// pt.y=10
			// *( (char*)&pt+p2 )=10;

	std::cout << p2 << std::endl;
	printf("%p\n", p2);
}

pointer to member data

 

  • 멤버 데이터를 가리키는 포인터(p)를 사용해서 "객체의 멤버 데이터에 접근하는 2가지 방법"

Pointer to member data

#include<iostream>

struct Point
{
	int x;
	int y;
};

int main()
{
	int Point::* p = &Point::y;

	Point obj;
	obj.*p = 10;	// obj.y=10
	(&obj)->*p = 10;

	std::invoke(p, obj) = 20;	// obj.y=20
	int n = std::invoke(p, obj);

	std::cout << n << std::endl;
	std::cout << obj.y << std::endl;
}
  • callable type
    • std::invoke() 를 사용할 수 있는 타입
    • 함수, 함수포인터, 멤버 함수포인터, 멤버 데이터포인터, 람다 표현식이 만드는 타입, operator() 함수를 제공하는 타입 등.

 

  • 알고리즘 함수가 사용하는 "정책(비교방식) 을 변경"하고 싶다.

algorithm 변경 방식

#include<iostream>

template<typename T>
const T& mymax(const T& obj1, const T& obj2)
{
	return obj1 < obj2 ? obj2 : obj1;
}

int main()
{
	std::string s1 = "abcde";
	std::string s2 = "xyz";

	auto ret1 = mymax(s1, s1);
	auto ret2 = mymax(s1.size(), s2.size());

	auto ret3 = mymax(s1, s2, [](auto& a, auto& b) { return a.size() < b.size(); });
	auto ret4 = mymax(s1, s2, [](auto& a) { return a.size(); });
							// f(s1) < f(s2)

	std::cout << ret1 << std::endl<<ret2 << std::endl;
}

 

  • Projection 개념 추가
    • myprojmax() 의 3번째 인자로 단항 조건 연상자 전달
      • 비교시 조건자의 결과를 비교
        • proj(obj1) < proj(obj2)
      • C++20 에서 "projection" 이라고 부르는 개념
    • 단한 조건자 대신 "멤버 함수의 포인터"를 보낼 수 없을까?
      • std::invoke() 를 사용하면 된다.
        • proj(obj1) : proj가 일반함수라면 ok. 멤버함수라면 error
        • std::invoke(proj, obj1) : proj가 일반함수 든 멤버함수든 ok
#include<iostream>

template<typename T>
const T& mymax(const T& obj1, const T& obj2)
{
	return obj1 < obj2 ? obj2 : obj1;
}

template<typename T, typename Proj>
const T& myprojmax(const T& obj1, const T& obj2, Proj proj)
{
	// 멤버함수의 경우 (obj2.*proj)() 이렇게 해야하므로 다른식의 구현이 필요하다. 
	// 하지만 아래 invokemax() 처럼 invoke를 사용하면 함수 하나로 사용 가능
	return proj(obj1) < proj(obj2) ? obj2 : obj1;
}

template<typename T, typename Proj>
const T& invokemax(const T& obj1, const T& obj2, Proj proj)
{
	return std::invoke(proj,obj1) < std::invoke(proj,obj2) ? obj2 : obj1;
}

int main()
{
	std::string s1 = "abcde";
	std::string s2 = "xyz";

	auto ret1 = mymax(s1, s1);
	auto ret2 = mymax(s1.size(), s2.size());

	auto ret3 = invokemax(s1, s2, [](auto& a) { return a.size(); });
	auto ret4 = invokemax(s1, s2, &std::string::size );

	std::cout << ret1 << std::endl << ret2 << std::endl
	std::cout << ret3 << std::endl << ret4 << std::endl;
}

 

  • Proj 는 생략 가능해야한다
    • defualt 인자가 필요
#include<iostream>

template<typename T, typename Proj=std::identity>
const T& mymax(const T& obj1, const T& obj2, Proj proj = {})
{
	return std::invoke(proj,obj1) < std::invoke(proj,obj2) ? obj2 : obj1;
}

int main()
{
	std::string s1 = "abcde";
	std::string s2 = "xyz";

	std::identity f;
	auto& r = f(s1);// 자기자신에 대한 참조를 반환

	std::cout << &s1 << std::endl;
	std::cout << &r << std::endl;

	auto ret = mymax(s1, s2);
	std::cout << ret << std::endl;
}
  • std::identity ( C++20 )
    • 전달받은 인자를 어떠한 변화도 없이 그대로 반환하는 함수객체
    • <functional>
    • C++20 부터 지원되지만 C++11문법으로도 구현가능( 단, [[nodiscard]]는 C++17 )
    • perfect forwarding 과 callable 테크닉 사용
struct identity
{
	template<class _Ty>
	[[nodiscard]] constexpr	_Ty&& operator() (_Ty& arg) const noexcept
	{
		return std::forward<_Ty>(arg);
	}
	using is_transparent = int;
};

 

  • mymax 함수 사용법
    • 멤버 함수 포인터 뿐 아니라 "멤버 변수 포인터도 std::invoke를 사용할 수 있다"
  • mymax를 사용하는 방법
    • projection 생략 : mymax(s1,s1);
    • 단항 조건자 : mymax(s1,s2, [](auto& a) { return a.size(); } );
    • 멤버함수 포인터 : mymax(s1,s1, &std::string::size);
    • 멤버변수 포인터 : mymax(s1,s2, &Point.y);
#include<iostream>

template<typename T, typename Proj=std::identity>
const T& mymax(const T& obj1, const T& obj2, Proj proj = {})
{
	return std::invoke(proj,obj1) < std::invoke(proj,obj2) ? obj2 : obj1;
}

struct Point
{
	int x, y;
};

int main()
{
	std::string s1 = "abcde";
	std::string s2 = "xyz";

	// mymax를 사용하는 방법
	auto ret1 = mymax(s1, s1);				   // 1. projection 생략
	auto ret2 = mymax(s1, s2, [](auto& a) { return a.size(); });// 2. 단항 조건자
	auto ret3 = mymax(s1, s2, &std::string::size);		   // 3. 멤버함수 포인터

	Point p1 = { 0,0 };
	Point p2 = { 1,2 };

	// function 을 전달하지 않고, y값만 가지고 비교하고싶을때도 가능
	auto ret4 = mymax(p1, p2, &Point::y);			   // 4. 멤버변수 포인터

	std::cout << ret4.x <<","<<ret4.y << std::endl;
}

 

  • 비교 함수(객체) 교체 가능하도록
    • projection 뿐 아니라 "비교 정책 자체를 교체" 할 수 있도록 "이항조건자(binary predicator)"를 함수 인자화 한다.
#include<iostream>

// 비교정책 자체가 교체가능하도록
template<typename T, class Comp=std::less<void>, typename Proj=std::identity>
const T& mymax(const T& obj1, const T& obj2, Comp comp= {}, Proj proj = {})
{
	return std::invoke(comp, std::invoke(proj,obj1), std::invoke(proj,obj2) ) ? obj2 : obj1;
}

int main()
{
	std::string s1 = "abcd";
	std::string s2 = "xyz";

	auto ret1=mymax(s1,s1);		// 객체만 전달
	auto ret2=mymax(s1,s2, std::greater{});	// 비교 함수객체 전달
	auto ret3=mymax(s1,s2, {}, &std::string::size);	// project 만 전달
	auto ret4=mymax(s1,s2, std::greater{}, &std::string::size);// 비교 함수객체, project 모두 전달

	std::cout << ret1 << std::endl;
	std::cout << ret2 << std::endl;
	std::cout << ret3 << std::endl;
	std::cout << ret4 << std::endl;

	std::cout<<std::max(s1, s2);
}
  • mymax 를 사용하는 방법
    • 객체만 전달 : mymax(s1,s1)
    • 비교 함수객체 전달 : mymax(s1,s2, std::greater{});
    • project 만 전달 : mymax(s1,s2, {}, &std::string::size);
    • 비교 함수객체, project 모두 전달 : mymax(s1,s2, std::greater{}, &std::string::size);
  • 왜, 템플릿의 디폴트인자를 "std::less<T> 가 아닌 std::less<void>" 로 했을까?

 

  • C++20에 도입된 Range Algorithm
    • <Algorithm> header
    • 알고리즘의 "비교 정책을 교체" 할 수 있다.
    • "Projection을 전달" 할 수 있다
    • 멤버함수 포인터와 멤버 데이터 포인터 모두 사용가능
    • 반복자 구간이 아닌 컨테이너를 전달 받는다.
    • std::range::max 는 함수 객체(템플릿) 이다.
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>


int main()
{
	std::string s1 = "abcd";
	std::string s2 = "xyz";

	auto ret1 = std::ranges::max(s1, s2, std::greater{}, &std::string::size);
	std::cout << ret1 << std::endl;

	std::vector<std::string> v = { "hello", "a", "xxx", "zz" };
	std::ranges::sort(v, std::greater{}, &std::string::size);

	for(auto& e : v)
		std::cout << e << std::endl;
}