malloc

 

  • malloc은 인자로 전달된 정수만큼 Byte 단위로 동적할당하여 void* 로 return 하는 역할을 합니다.




new

 

  • malloc은 함수지만 new는 연산자입니다.

 

  • 데이터 타입의 크기만큼 메모리 동적할당을 합니다.

 

  • 인자로 전달된 타입이 구조체 또는 클래스라면 동적할당 후 생성자를 호출합니다.

 

  • return 시 해당 데이터 타입으로 캐스팅합니다.




new와 배열

 

#include <iostream>
#include <Windows.h>

class CPlayer {
public:
    CPlayer() {
        std::cout << "생성자";
    }
    ~CPlayer() {
        std::cout << "소멸자";
    }
};

int main()
{
    CPlayer* p = new CPlayer[100];

    delete[] p;

    return 1;
}

 

  • new 연산자는 동적할당할 객체를 배열 단위로 할당할 수 있습니다. 그리고 배열의 크기만큼 생성자를 호출하며 delete[]에서도 소멸자를 호출해주기 위해서 동적할당한 인스턴스의 앞 8Byte에 몇개의 인스턴스를 생성하였는지를 저장하여 delete[]에서 몇개의 소멸자가 호출되어야 하는지를 판단합니다.




얕은 복사

 

class CPlayer {
public:
    CPlayer(const char* name)
    {
        mName = static_cast<char*>(malloc(strlen(name) + 1));

        strcpy_s(mName, strlen(name) + 1, name);
    }

    ~CPlayer() {
        free(mName);
    }

    const char* GetName() {
        return mName;
    }

private:
    char* mName;
};

int main()
{
    CPlayer player("devhun");

    CPlayer p = player;

    std::cout << p.GetName();

    return 1;
}

 

  • 위 코드는 default 복사 생성자로 인해서 얕은 복사가 발생됩니다. 복사 생성자를 별도로 정의하지 않으면 멤버변수를 그대로 복사하는데, 그럴경우 malloc으로 할당받은 메모리를 가리키는 포인터가 2개로 늘어나는 문제가 발생됩니다.




깊은 복사

 

#include <iostream>
#include <Windows.h>

class CPlayer {
public:
    CPlayer(const char* name)
    {
        mName = static_cast<char*>(malloc(strlen(name) + 1));

        strcpy_s(mName, strlen(name) + 1, name);
    }

    CPlayer(const CPlayer& player) {

        mName = static_cast<char*>(malloc(strlen(player.mName) + 1));

        strcpy_s(mName, strlen(player.mName) + 1, player.mName);
    }

    ~CPlayer() {
        free(mName);
    }

    const char* GetName() {
        return mName;
    }

private:
    char* mName;
};

int main()
{
    CPlayer player("devhun");

    CPlayer p = player;

    std::cout << p.GetName();

    return 1;
}

 

  • 얕은 복사로 인해서 포인터가 복사되는 현상을 없애기 위해서는 복사 생성자를 직접 정의하여 깊은 복사를 하도록 구현해야 합니다. 깊은 복사는 인자로 전달된 인스턴스의 포인터 부분을 동적할당하여 직접 초기화하는 작업을 말합니다.




RAII( Resource Acquisition Is Initialization )이란?


  • RAII( Resource Acquisition Is Initialization )는 리소스 획득은 초기화다 라는 의미를 가지고 있습니다. RAII 기법은 생성자에서 리소스를 획득하고 해당 인스턴스가 스코프를 벗어나면 자동으로 소멸자가 호출되면서 리소스를 해제하는 기법을 말합니다. RAII 패턴이 있기 때문에 C++의 창시자 비야네 스트롭스트룹은 다른 언어에서 사용되는 try/catch에서 finally를 C++에 도입하지 않는다고 하였습니다.




RAII 사용 예시


#include <iostream>
#include <Windows.h>

CRITICAL_SECTION sc;

class CSC {
public:

    CSC(CRITICAL_SECTION& sc)
        :mSC(sc)
    {
        std::cout << "Enter~\n";
        EnterCriticalSection(&mSC);
    }

    ~CSC()
    {
        std::cout << "Leave~\n";
        LeaveCriticalSection(&mSC);
    }

private:

    CRITICAL_SECTION& mSC;
};

int main()
{
    InitializeCriticalSection(&sc);

    {
        CSC sc(sc);
    }

    return 1;
}


  • 위와 같이 동기화 객체를 사용할 때 RAII 디자인 패턴을 사용하면 프로그래머의 실수로 동기화 객체를 해제하는 것을 깜빡하는 실수를 없앨 수 있습니다.

  • 스마트 포인터 또한 RAII 디자인 패턴을 바탕으로 만들어졌습니다.




상속 관계에서 호출할 부모 생성자를 지정하는 방법

#include <iostream>

class CParent {
public:

    CParent()
    {
        std::cout << "Parent\n";
    }

    CParent(const char* comment)
    {
        std::cout << comment;
    }
};

class CChild :private CParent {
public:

    CChild()
    {
        std::cout << "Child\n";
    }

    CChild(const char* comment)
        :CParent(comment)
    {
    }

};

int main()
{
    CChild child("Hello");

    return 1;
}

 

  • 위와같이 상속 관계에서 자식 생성자에서 부모 클래스의 어떤 생성자를 호출할지를 이니셜라이저를 통해 지정할 수 있습니다. 만약, 지정하지 않는 다면은 인자를 아무것도 받지 않는 부모 생성자가 호출됩니다.




const 멤버 함수


#include <iostream>

class CPlayer {
public:

    CPlayer(int age)
        :mAge(age)
    {}

    ~CPlayer() = default;

    void SetAge(int age) {
        mAge = age;
    }

    int GetAge() const {
        return mAge;
    }

private:

    int mAge;
};


int main()
{
    const CPlayer p(20);

    std::cout << p.GetAge();

    return 1;
}

  • const 객체는 const 함수로 정의된 멤버함수만을 호출할 수 있습니다. 또한 const 멤버 함수 내부에서는 멤버 변수를 수정할 수 없습니다.

  • 만약, 멤버 변수가 정의될 때 mutable 키워드를 사용했다면 const 멤버 함수 내부에서도 수정할 수 있습니다.




매크로 함수란?

 

#include <iostream>
#define SQUARE(x) (x * x)

int main()
{
    std::cout << SQUARE(3);
}

 

  • 매크로 함수는 전처리 단계에서 호출부가 정의한 형식 그대로 치환됩니다. 그렇기 때문에 함수 호출로 인한 추가적인 명령어 처리가 없기 때문에 성능상 이점이 있을 수 있습니다.




매크로 함수의 단점

 

#include <iostream>
#define SQUARE(x) (x * x)

int main()
{
    std::cout << SQUARE(++3);

    std::cout << SQUARE("ASDF");
}

 

  • 매크로 함수는 인자로 전달된 형태 그대로 치환하고 데이터 타입을 지정할 수 없기 때문에 사용자가 의도하지 않은 결과가 return 될 수 있습니다.




inline 함수란?

 

#include <iostream>

inline int SQUARE(int x) {
    return  x * x;
}

int main()
{
    std::cout << SQUARE(10);
}

 

  • 매크로 함수를 이용한 함수의 인라인화는 전처리기에 의해서 처리되지만, inline 키워드를 통해 함수를 정의할 경우 컴파일러에 의해서 인라인 처리가 됩니다. 덕분에 데이터 타입을 지정할 수 있고 매크로 함수처럼 형태 그대로 치환되는 형식이 아니기 때문에 매크로 함수보다 이점이 있습니다.




inline 함수의 단점

 

  • inline 키워드를 사용한다고 해서 무조건적으로 inline 처리가 되지 않습니다. 컴파일러 판단에 의해서 inline에 이점이 있을 때 inline 처리가 됩니다.

 

  • 만약, 무조건적으로 inline 처리가 된다 하면 메모리 사용량이 증가되어 관리해야할 페이지 개수가 늘어나고 명령어 캐시 hit율이 떨어지기 때문에 성능이 오히려 떨어질 수 있습니다. 그렇기 때문에 코드양이 적은 함수만이 inline 처리 되는것이 성능에 유리합니다.




이터레이션 프로토콜

 

  • ES6에서는 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 for...of문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화했습니다. 이터레이션 프로토콜에는 이터러블 프로토콜과 이터레이터 프로토콜이 있습니다.




이터러블 프로토콜(iterable protocol)

 

  • Well-known Symbol인 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속 받은 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다. 이러한 규약을 이터러블 프로토콜이라 하며, 이터러블 프로토콜을 준수한 객체를 이터러블이라 합니다. 이터러블은 for...of 문으로 순회할 수 있으며 스프레드 문법과 배열 디스트럭처 할당의 대상으로 사용할 수 있습니다.




이터레이터 프로토콜(iterator protocol)

 

  • 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다.
    이터레이터는 next 메서드를 소유하며 next 메서드를 호출하면 이터러블을 순회하며 value와 done프로퍼티를 갖는 Iterator Result Object를 반환합니다. 이러한 규약을 이터레이터 프로토콜이라 하며, 이터레이터 프로토콜을 준수한 객체를 이터레이터라 합니다. 이터레이터는 이터러블의 요소를 탐색하기 위한 포인터 역할을 합니다.




이터러블

 

const isIterable = (v) => console.log(typeof v[Symbol.iterator] === "function");

isIterable([]); // true
isIterable(""); // true
isIterable(new Map()); // true
isIterable(new Set()); // true
isIterable({}); // false

 

const array = [1, 2, 3];

for (const item of array) {
  console.log(iter);
}

console.log([...array]);

 

  • 이터러블 프로토콜을 준수한 객체를 이터러블이라 합니다. 즉, 이터러블은 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체를 말합니다. 이터러블은 for...of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있습니다.




이터레이터

 

const array = [1, 2, 3];

const iterator = array[Symbol.iterator];

console.log(iterator.next()); // [value: 1, done: false]
console.log(iterator.next()); // [value: 2, done: false]
console.log(iterator.next()); // [value: 3, done: false]
console.log(iterator.next()); // [value: undefined, done: true]

 

  • 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다 이터러블의 Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖습니다. next 메서드를 호출하면 이터러블을 순차적으로 한 단계씩 순회하며 순회 결과를 나타내는 iterator result object를 반환합니다.




빌트인 이터러블

 

빌트인 이터러블
Symbol.iterator 메서드
Array Array.prototype[Symbol.iterator]
String String.prototype[Symbol.iterator]
Map Map.prototype[Symbol.iterator]
Set Set.prototype[Symbol.iterator]
TypedArray TypedArray.prototype[Symbol.iterator]
arguments arguments[Symbol.iterator]

 

  • 자바스크립트는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공합니다. 위 표에 나와있는 표준 빌트인 객체들은 빌트인 이터러블입니다.




for...of 문

 

  • for...of 문은 이터러블을 순회하면서 이터러블의 요소를 변수에 할당합니다. for...of문과 for...in 문은 다르며 for...in문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트가 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거합니다.
    for...of문은 내부적으로 이터레이터의 next 메서드를 호출하여 이터러블을 순회하며 enxt 메서드가 반환한 iterator result object의 value 프로퍼티 값을 할당하는 식으로 처리하고 done 프로퍼티 값이 false이면 이터러블의 순회를 계속하고 true이면 이터러블의 순회를 중단합니다.




이터러블과 유사 배열 객체

 

const arrayList = {
  0: 1,
  1: 2,
  2: 3,
  length: 3,
};

 

  • 유사 배열 객체는 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말합니다.

 

  • 유사 배열 객체라고 해서 무조건 이터러블 객체는 아닙니다. Symbol.iterator 프로퍼티를 이용해서 이터레이션 프로토콜을 준수하는 메서드를 구현해야 합니다.




자바스크립트 배열이란?


const arr = [1, 2, 3];

arr.constructor === Array;
Object.getPrototypeOf(arr) === Array.prototype;

  • 배열이란 인덱스에 대응하는 데이터들로 이루어진 자료구조를 말합니다.

  • 자바스크립트에서는 배열 타입이 별도로 존재하지 않으며 배열은 객체입니다. 배열은 배열 리터럴, Array 생성자 함수, Array of, Array.from 메서드로 생성할 수 있습니다.




자바스크립트 배열의 특징


const arr = [, 2, , 4];

// [empty, 2, empty, 3] 출력
console.log(arr);

// 4 출력
console.log(arr.length);

  • 자바스크립트 배열은 해시 테이블로 구현된 객체이므로 인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 떨어지지만 요소를 삽입, 삭제하는 경우에는 일반적인 배열보다 빠른 성능을 기대할 수 있습니다. 자바스크립트는 배열의 중간 요소를 비울 수 있는 희소 배열을 문법적으로 허용합니다.
    희소 배열은 연속적인 값의 집합이라는 배열의 기본적인 개념과 맞지 않으며, 성능에도 좋지 않은 영향을 줍니다.




모던 자바스크립트 배열


  • 최적화가 잘 되어 있는 모던 자바스크립트 엔진은 요소의 타입이 일치하는 배열을 생성할 때 일반적인 의미의 배열처럼 연속된 메모리 공간을 확복합니다. 배열을 생성할 때 희소 배열을 생성하지 않도록 주의하고, 배열에는 같은 타입의 요소를 연속적으로 위치시키는 것이 최선입니다.




배열 생성 방법

// 배열 리터럴
let arr = [1, 2, 3];

// length 5개인 배열 생성
arr = new Array(5);

// [1,2,3] 요소를 가진 배열 생성
arr = new Array(1, 2, 3);

// 요소가 1인 배열 생성
arr = Array.of(1);

// 유사 배열 객체, 이터러블로 배열 생성
arr = Array.from({ length: 2, 0: "a", 1: "b" });

arr = Array.from("Hello");

  • Array.of, Array.from은 ES6에 추가된 배열 생성 방법입니다.




유사 배열 객체(array-like object)


  • 유사 배열 객체는 마치 배열처럼 인덱스로 프로퍼티 값에 전급할 수 있고 length 프로퍼티를 갖는 객체를 말합니다.




이터러블(iterable object)


  • 이터러블 객체는 Symbol.iterator 메서드를 구현하여 for...for 문으로 순회할 수 있으며, 스프레드 배열 디스트럭처링 할당의 대상으로 사용할 수 있는 객체를 말합니다.




배열 요소 추가와 삭제


const arr = [20, 30, 40];

// 뒤에 요소 추가
arr.push(100);

// 뒤에 요소 삭제
arr.pop();

// 배열 요소 삭제
delete arr[0];

arr.splice(0, 1);

// [30, 40] 출력
console.log(arr);

  • 배열은 객체이며 배열의 특정 요소를 삭제하기 위해서는 delete 연산자를 사용할 수 있습니다. 하지만, 이때 희소배열이 되며 length 프로퍼티 값은 변하지 않습니다. 그렇기 때문에 splice 메서드를 사용하는 것이 더 좋습니다.




모듈(module)이란?


  • 모듈이란 애플리케이션을 구성하는 개별적 요소로서 재사용이 가능한 코드 모음을 말합니다. Javascript에서의 모듈은 기능을 기준으로 파일 단위로 분리합니다. 이때 모듈이 성립하려면 모듈은 자신만의 파일 스코프(모듈 스코프)를 가질 수 있어야 합니다.




모듈 export


export const hello = () => {
  console.log("Hello World");
};

  • 자신만의 모듈(파일) 스코프를 갖는 모듈 내부에 있는 코드들은 기본적으로 비공개 상태입니다. 하지만, 모듈안에 있는 기능을 사용자가 사용하거나 다른 모듈에서 사용하기 위해서는 선택적으로 공개할 수 있어야 합니다. 이렇게 일부 코드를 공개하기 위해서는 공개할 코드에 export 키워드를 사용하고 다른 모듈에서는 사용할 외부 모듈의 코드에 대해서 import하여 해당 코드를 접근할 수 있도록 할 수 있습니다.




브라우저와 모듈 시스템


  • 자바스크립트는 웹 페이지의 보조적인 역할을 목적으로 태어났기 때문에 다른 일반 프로그래밍 언어보다 지원하는 기능이 많지 않았습니다. 그래서 모듈 시스템이 지원되지 않았고 브라우저에서 각 파일로 분리된 자바스크립트 파일을 script 태그로 불러와도 하나의 파일처럼 동작하기 때문에 전역 코드의 오염이 발생되는 경우가 많았습니다.(Node.js는 모듈 시스템을 지원하고 있습니다.)




브라우저에서 모듈 시스템 사용하기


<!DOCTYPE html>
<html>
  <body>
    <script type="module" scr="foo.mjs"></script>
    <script type="module" scr="bar.mjs"></script>
  </body>
</html>

  • ES6 이후 부터는 브라우저에서도 모듈 시스템을 사용할 수 있으며, 모듈로서 사용할 파일의 확장자 이름은 .mjs로 해야합니다. 그리고 script 태그의 속성으로 type="module"을 지정하면됩니다.




'Programming Language > JavaScript' 카테고리의 다른 글

Javascript의 이터레이션 프로토콜이란?  (0) 2022.12.20
Javascript 배열  (0) 2022.12.20
Javascript의 async/await 사용법  (0) 2022.12.19
Javascirpt fetch 사용 방법  (0) 2022.12.19
Javascript의 Promise란?  (0) 2022.12.19

async/await


  • ES8에 도입된 async/await은 Promise를 기반으로 동작하여 가독성 좋게 비동기 처리를 동기처럼 보이도록 구현할 수 있습니다.




async/await 사용법


const fetchTodo = async () => {
  const res = await fetch(url);
  const todo = await res.json();
  console.log(todo);
};

fetchTodo();

  • 위와 같이 Promise의 then과 같은 후속 처리 메서드를 호출할 필요없이 마치 동기처럼 보이도록 구현할 수 있습니다.




async 함수


async function foo(n) {
  return n;
}
foo(1).then((res) => console.log(res)); // return 1

const bar = async function (n) {
  return n;
};
bar(2).then((res) => console.log(res)); // return 2

const baz = async (n) => n;
baz(3).then((res) => console.log(res)); // return 3

const obj = {
  async foo(n) {
    return n;
  },
};
obj.foo(4).then((res) => console.log(res));

class MyClass {
  async bar(n) {
    return n;
  }
}
const myClass = new MyClass();
myClass.bar(5).then((res) => console.log(res));

  • await 키워드는 반드시 async 함수 내부에서 사용해야 합니다. async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스를 반환합니다. async 함수가 명시적으로 프로미스를 반환하지 않더라도 async 함수는 암묵적으로 반환값을 resolve하는 프로미스를 반환합니다.

  • 클래스의 constructor 메서드는 async 메서드가 될수 없습니다. 클래스의 constructor 메서드는 인스턴스를 반환해야 하지만 async 함수는 언제나 프로미스를 반환해야 하기 때문입니다.




await 키워드


(async () => {
  const res = await fetch(url);
  console.log(res.status);
})();

  • await 키워드는 Promise가 settled 상태(비동기 처리가 수행된 상태)가 될 때까지 대기하다가 settled 상태가 되면 Promise가 resolve한 처리 결과를 반환합니다. await 키워드는 반드시 Promise 앞에 사용해야 합니다.




await 키워드 사용시 주의할 점


const foo = async () => {
  const a = await new Promise((resolve) => setTimeout(() => resolve(1), 3000));
  const b = await new Promise((resolve) => setTimeout(() => resolve(2), 1000));
  const c = await new Promise((resolve) => setTimeout(() => resolve(3), 1000));
  console.log("Hello Wolrd");
};

// 약 6초 소요
foo();
console.log("finish");

const foo = async () => {
  const res = await Promise.all([
    new Promise((resolve) => setTimeout(() => resolve(1), 3000)),
    new Promise((resolve) => setTimeout(() => resolve(2), 1000)),
    new Promise((resolve) => setTimeout(() => resolve(3), 1000)),
  ]);

  console.log(res);
};

// 약 3초 소요
foo();
console.log("finish");

  • foo 함수를 호출할 경우 await으로 인해 6초 이후 "Hello World"가 출력됩니다. 즉, 위와 같이 각각의 Promise가 서로 연관되지 않고 처리 순서를 지킬 필요가 없다면은 일일이 await을 거는 것보다는 Promise.all을 이용해서 한번에 요청 후 요청한 비동기 요청이 완료되었을 때 응답을 처리하는 것이 좋습니다.




에러 핸들링


(async () => {
  try {
    const res = await fetch(url);
  } catch (err) {
    console.log(err);
  }
})();

  • Prmoise를 후속 처리 메서드로 처리할 경우 try/catch로 에러 핸들링하기 적합하지 않습니다. 하지만, async/await에서 에러 처리는 try/catch를 사용하기에 적합합니다.

  • async 함수 내에서 err가 발생되었을 경우 에러를 reject하는 Promise를 반환합니다. 그렇기 때문에 async 함수 외부에서 .catch 후속 처리 메서드로 에러를 핸들링 할 수 있습니다.




'Programming Language > JavaScript' 카테고리의 다른 글

Javascript 배열  (0) 2022.12.20
자바스크립트의 모듈(module)이란?  (0) 2022.12.19
Javascirpt fetch 사용 방법  (0) 2022.12.19
Javascript의 Promise란?  (0) 2022.12.19
JSON(Javascript Object Notation)이란?  (0) 2022.12.19

+ Recent posts